@living-architecture/riviere-cli 0.8.0 → 0.8.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.
Files changed (3) hide show
  1. package/dist/bin.js +496 -65
  2. package/dist/index.js +502 -64
  3. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -25075,8 +25075,9 @@ Examples:
25075
25075
 
25076
25076
  // src/features/extract/entrypoint/extract.ts
25077
25077
  import {
25078
- existsSync as existsSync3,
25079
- readFileSync as readFileSync3
25078
+ existsSync as existsSync4,
25079
+ readFileSync as readFileSync4,
25080
+ writeFileSync
25080
25081
  } from "node:fs";
25081
25082
  import {
25082
25083
  dirname as dirname3,
@@ -26153,14 +26154,192 @@ function requireRule(rule, ruleName, moduleName) {
26153
26154
  return rule;
26154
26155
  }
26155
26156
 
26157
+ // ../riviere-extract-ts/dist/platform/domain/string-transforms/transforms.js
26158
+ function stripSuffix(value, suffix) {
26159
+ if (value.endsWith(suffix)) {
26160
+ return value.slice(0, -suffix.length);
26161
+ }
26162
+ return value;
26163
+ }
26164
+ function stripPrefix(value, prefix) {
26165
+ if (value.startsWith(prefix)) {
26166
+ return value.slice(prefix.length);
26167
+ }
26168
+ return value;
26169
+ }
26170
+ function toLowerCase(value) {
26171
+ return value.toLowerCase();
26172
+ }
26173
+ function toUpperCase(value) {
26174
+ return value.toUpperCase();
26175
+ }
26176
+ function kebabToPascal(value) {
26177
+ return value.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
26178
+ }
26179
+ function pascalToKebab(value) {
26180
+ const transformed = value.replaceAll(/([A-Z])/g, "-$1").toLowerCase();
26181
+ return transformed.startsWith("-") ? transformed.slice(1) : transformed;
26182
+ }
26183
+ function applyTransforms(value, transform2) {
26184
+ const transformers = [];
26185
+ if (transform2.stripSuffix !== void 0) {
26186
+ const suffix = transform2.stripSuffix;
26187
+ transformers.push((v) => stripSuffix(v, suffix));
26188
+ }
26189
+ if (transform2.stripPrefix !== void 0) {
26190
+ const prefix = transform2.stripPrefix;
26191
+ transformers.push((v) => stripPrefix(v, prefix));
26192
+ }
26193
+ if (transform2.toLowerCase === true) {
26194
+ transformers.push(toLowerCase);
26195
+ }
26196
+ if (transform2.toUpperCase === true) {
26197
+ transformers.push(toUpperCase);
26198
+ }
26199
+ if (transform2.kebabToPascal === true) {
26200
+ transformers.push(kebabToPascal);
26201
+ }
26202
+ if (transform2.pascalToKebab === true) {
26203
+ transformers.push(pascalToKebab);
26204
+ }
26205
+ return transformers.reduce((acc, fn) => fn(acc), value);
26206
+ }
26207
+
26156
26208
  // ../riviere-extract-ts/dist/platform/domain/ast-literals/literal-detection.js
26157
26209
  import { SyntaxKind } from "ts-morph";
26210
+ var ExtractionError = class extends Error {
26211
+ location;
26212
+ constructor(message, file2, line) {
26213
+ super(`${message} at ${file2}:${line}`);
26214
+ this.name = "ExtractionError";
26215
+ this.location = {
26216
+ file: file2,
26217
+ line
26218
+ };
26219
+ }
26220
+ };
26221
+ function extractString(expression) {
26222
+ return expression.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
26223
+ }
26224
+ function extractNumber(expression) {
26225
+ return Number(expression.getText());
26226
+ }
26227
+ function buildExtractionResult(expression) {
26228
+ const syntaxKind = expression.getKind();
26229
+ switch (syntaxKind) {
26230
+ case SyntaxKind.StringLiteral:
26231
+ return {
26232
+ kind: "string",
26233
+ value: extractString(expression)
26234
+ };
26235
+ case SyntaxKind.NumericLiteral:
26236
+ return {
26237
+ kind: "number",
26238
+ value: extractNumber(expression)
26239
+ };
26240
+ case SyntaxKind.TrueKeyword:
26241
+ return {
26242
+ kind: "boolean",
26243
+ value: true
26244
+ };
26245
+ case SyntaxKind.FalseKeyword:
26246
+ return {
26247
+ kind: "boolean",
26248
+ value: false
26249
+ };
26250
+ default:
26251
+ return void 0;
26252
+ }
26253
+ }
26254
+ function throwMissingInitializer(file2, line) {
26255
+ throw new ExtractionError("No initializer found", file2, line);
26256
+ }
26257
+ function throwNonLiteralValue(expression, file2, line) {
26258
+ throw new ExtractionError(`Non-literal value detected (${expression.getKindName()}): ${expression.getText()}. Only inline literals (strings, numbers, booleans) are supported`, file2, line);
26259
+ }
26260
+ function extractLiteralValue(expression, file2, line) {
26261
+ if (expression === void 0) {
26262
+ throwMissingInitializer(file2, line);
26263
+ }
26264
+ const result = buildExtractionResult(expression);
26265
+ if (result === void 0) {
26266
+ throwNonLiteralValue(expression, file2, line);
26267
+ }
26268
+ return result;
26269
+ }
26158
26270
 
26159
26271
  // ../riviere-extract-ts/dist/domain/value-extraction/evaluate-extraction-rule-generic.js
26160
26272
  import { SyntaxKind as SyntaxKind2 } from "ts-morph";
26161
26273
 
26162
26274
  // ../riviere-extract-ts/dist/domain/value-extraction/evaluate-extraction-rule.js
26163
26275
  import { SyntaxKind as SyntaxKind3 } from "ts-morph";
26276
+ function literal2(value) {
26277
+ return { value };
26278
+ }
26279
+ function evaluateLiteralRule(rule) {
26280
+ return literal2(rule.literal);
26281
+ }
26282
+ function evaluateFromClassNameRule(rule, classDecl) {
26283
+ const className = classDecl.getName() ?? "";
26284
+ if (rule.fromClassName === true) {
26285
+ return { value: className };
26286
+ }
26287
+ const transform2 = rule.fromClassName.transform;
26288
+ if (transform2 === void 0) {
26289
+ return { value: className };
26290
+ }
26291
+ return { value: applyTransforms(className, transform2) };
26292
+ }
26293
+ function evaluateFromFilePathRule(rule, filePath) {
26294
+ const { pattern, capture, transform: transform2 } = rule.fromFilePath;
26295
+ const regex = new RegExp(pattern);
26296
+ const match2 = regex.exec(filePath);
26297
+ if (match2 === null) {
26298
+ throw new ExtractionError(`Pattern '${pattern}' did not match file path '${filePath}'`, filePath, 0);
26299
+ }
26300
+ const capturedValue = match2[capture];
26301
+ if (capturedValue === void 0) {
26302
+ throw new ExtractionError(`Capture group ${capture} out of bounds. Pattern has ${match2.length - 1} capture groups`, filePath, 0);
26303
+ }
26304
+ if (transform2 === void 0) {
26305
+ return { value: capturedValue };
26306
+ }
26307
+ return { value: applyTransforms(capturedValue, transform2) };
26308
+ }
26309
+ function findPropertyInHierarchy(classDecl, propertyName, isStatic) {
26310
+ const properties = isStatic ? classDecl.getStaticProperties() : classDecl.getInstanceProperties();
26311
+ const property = properties.find((p) => p.getName() === propertyName);
26312
+ if (property !== void 0 && "getInitializer" in property) {
26313
+ const sourceFile = classDecl.getSourceFile();
26314
+ return {
26315
+ initializer: property.getInitializer(),
26316
+ filePath: sourceFile.getFilePath(),
26317
+ line: property.getStartLineNumber()
26318
+ };
26319
+ }
26320
+ const baseClass = classDecl.getBaseClass();
26321
+ if (baseClass === void 0) {
26322
+ return void 0;
26323
+ }
26324
+ return findPropertyInHierarchy(baseClass, propertyName, isStatic);
26325
+ }
26326
+ function evaluateFromPropertyRule(rule, classDecl) {
26327
+ const { name, kind, transform: transform2 } = rule.fromProperty;
26328
+ const isStatic = kind === "static";
26329
+ const propertyInfo = findPropertyInHierarchy(classDecl, name, isStatic);
26330
+ if (propertyInfo === void 0) {
26331
+ const sourceFile = classDecl.getSourceFile();
26332
+ throw new ExtractionError(`Property '${name}' not found on class '${classDecl.getName() ?? "anonymous"}'`, sourceFile.getFilePath(), classDecl.getStartLineNumber());
26333
+ }
26334
+ const literalResult = extractLiteralValue(propertyInfo.initializer, propertyInfo.filePath, propertyInfo.line);
26335
+ if (transform2 === void 0) {
26336
+ return { value: literalResult.value };
26337
+ }
26338
+ if (typeof literalResult.value !== "string") {
26339
+ return { value: literalResult.value };
26340
+ }
26341
+ return { value: applyTransforms(literalResult.value, transform2) };
26342
+ }
26164
26343
 
26165
26344
  // ../../node_modules/.pnpm/@isaacs+balanced-match@4.0.1/node_modules/@isaacs/balanced-match/dist/esm/index.js
26166
26345
  var balanced = (a, b, str) => {
@@ -27719,6 +27898,158 @@ function matchesGlob(path2, pattern) {
27719
27898
  return minimatch(path2, pattern);
27720
27899
  }
27721
27900
 
27901
+ // ../riviere-extract-ts/dist/domain/value-extraction/enrich-components.js
27902
+ import { posix as posix2 } from "node:path";
27903
+ function findMatchingModule2(filePath, modules, globMatcher, configDir) {
27904
+ const normalized = filePath.replaceAll(/\\+/g, "/");
27905
+ const pathToMatch = posix2.relative(configDir.replaceAll(/\\+/g, "/"), normalized);
27906
+ return modules.find((m) => globMatcher(pathToMatch, m.path));
27907
+ }
27908
+ function isDetectionRule2(rule) {
27909
+ if (typeof rule !== "object" || rule === null) {
27910
+ return false;
27911
+ }
27912
+ return "find" in rule && "where" in rule;
27913
+ }
27914
+ function getBuiltInRule(module, componentType) {
27915
+ const ruleMap = {
27916
+ api: module.api,
27917
+ useCase: module.useCase,
27918
+ domainOp: module.domainOp,
27919
+ event: module.event,
27920
+ eventHandler: module.eventHandler,
27921
+ ui: module.ui
27922
+ };
27923
+ const rule = ruleMap[componentType];
27924
+ if (isDetectionRule2(rule)) {
27925
+ return rule;
27926
+ }
27927
+ return void 0;
27928
+ }
27929
+ function findDetectionRule(module, componentType) {
27930
+ const builtInTypes = [
27931
+ "api",
27932
+ "useCase",
27933
+ "domainOp",
27934
+ "event",
27935
+ "eventHandler",
27936
+ "ui"
27937
+ ];
27938
+ if (builtInTypes.includes(componentType)) {
27939
+ return getBuiltInRule(module, componentType);
27940
+ }
27941
+ return module.customTypes?.[componentType];
27942
+ }
27943
+ function isLiteralRule(rule) {
27944
+ return "literal" in rule;
27945
+ }
27946
+ function isFromClassNameRule(rule) {
27947
+ return "fromClassName" in rule;
27948
+ }
27949
+ function isFromFilePathRule(rule) {
27950
+ return "fromFilePath" in rule;
27951
+ }
27952
+ function isFromPropertyRule(rule) {
27953
+ return "fromProperty" in rule;
27954
+ }
27955
+ function findClassAtLine(project, draft) {
27956
+ const sourceFile = project.getSourceFile(draft.location.file);
27957
+ if (sourceFile === void 0) {
27958
+ throw new ExtractionError(`Source file '${draft.location.file}' not found in project`, draft.location.file, draft.location.line);
27959
+ }
27960
+ const classDecl = sourceFile.getClasses().find((c) => c.getStartLineNumber() === draft.location.line);
27961
+ if (classDecl === void 0) {
27962
+ throw new ExtractionError(`No class declaration found at line ${draft.location.line}`, draft.location.file, draft.location.line);
27963
+ }
27964
+ return classDecl;
27965
+ }
27966
+ function evaluateClassRule(rule, classDecl) {
27967
+ if (isFromClassNameRule(rule)) {
27968
+ return evaluateFromClassNameRule(rule, classDecl);
27969
+ }
27970
+ if (!isFromPropertyRule(rule)) {
27971
+ throw new ExtractionError("Unsupported extraction rule type for class-based component", classDecl.getSourceFile().getFilePath(), classDecl.getStartLineNumber());
27972
+ }
27973
+ return evaluateFromPropertyRule(rule, classDecl);
27974
+ }
27975
+ function evaluateRule(rule, draft, project) {
27976
+ if (isLiteralRule(rule)) {
27977
+ return evaluateLiteralRule(rule);
27978
+ }
27979
+ if (isFromFilePathRule(rule)) {
27980
+ return evaluateFromFilePathRule(rule, draft.location.file);
27981
+ }
27982
+ const classDecl = findClassAtLine(project, draft);
27983
+ return evaluateClassRule(rule, classDecl);
27984
+ }
27985
+ function componentWithEmptyMetadata(draft) {
27986
+ return {
27987
+ enriched: {
27988
+ ...draft,
27989
+ metadata: {}
27990
+ },
27991
+ failures: []
27992
+ };
27993
+ }
27994
+ function extractMetadataFields(extractBlock, draft, project) {
27995
+ const metadata = {};
27996
+ const missing = [];
27997
+ const failures = [];
27998
+ for (const [fieldName, extractionRule] of Object.entries(extractBlock)) {
27999
+ try {
28000
+ metadata[fieldName] = evaluateRule(extractionRule, draft, project).value;
28001
+ } catch (error48) {
28002
+ const errorMessage = error48 instanceof Error ? error48.message : String(error48);
28003
+ failures.push({
28004
+ component: draft,
28005
+ field: fieldName,
28006
+ error: errorMessage
28007
+ });
28008
+ missing.push(fieldName);
28009
+ }
28010
+ }
28011
+ return {
28012
+ metadata,
28013
+ missing,
28014
+ failures
28015
+ };
28016
+ }
28017
+ function enrichSingleComponent(draft, config2, project, globMatcher, configDir) {
28018
+ const module = findMatchingModule2(draft.location.file, config2.modules, globMatcher, configDir);
28019
+ if (module === void 0) {
28020
+ return componentWithEmptyMetadata(draft);
28021
+ }
28022
+ const detectionRule = findDetectionRule(module, draft.type);
28023
+ if (detectionRule?.extract === void 0) {
28024
+ return componentWithEmptyMetadata(draft);
28025
+ }
28026
+ const extracted = extractMetadataFields(detectionRule.extract, draft, project);
28027
+ const enriched = {
28028
+ ...draft,
28029
+ metadata: extracted.metadata
28030
+ };
28031
+ if (extracted.missing.length > 0) {
28032
+ enriched._missing = extracted.missing;
28033
+ }
28034
+ return {
28035
+ enriched,
28036
+ failures: extracted.failures
28037
+ };
28038
+ }
28039
+ function enrichComponents(draftComponents, config2, project, globMatcher, configDir) {
28040
+ const allComponents = [];
28041
+ const allFailures = [];
28042
+ for (const draft of draftComponents) {
28043
+ const result = enrichSingleComponent(draft, config2, project, globMatcher, configDir);
28044
+ allComponents.push(result.enriched);
28045
+ allFailures.push(...result.failures);
28046
+ }
28047
+ return {
28048
+ components: allComponents,
28049
+ failures: allFailures
28050
+ };
28051
+ }
28052
+
27722
28053
  // src/features/extract/commands/config-loader.ts
27723
28054
  import {
27724
28055
  dirname as dirname2,
@@ -27850,6 +28181,44 @@ function expandModuleRefs(config2, configDir) {
27850
28181
  };
27851
28182
  }
27852
28183
 
28184
+ // src/features/extract/commands/draft-component-loader.ts
28185
+ import {
28186
+ existsSync as existsSync3,
28187
+ readFileSync as readFileSync3
28188
+ } from "node:fs";
28189
+ var DraftComponentLoadError = class extends Error {
28190
+ constructor(message) {
28191
+ super(message);
28192
+ this.name = "DraftComponentLoadError";
28193
+ Error.captureStackTrace?.(this, this.constructor);
28194
+ }
28195
+ };
28196
+ function isDraftComponentArray(value) {
28197
+ if (!Array.isArray(value)) return false;
28198
+ return value.every(
28199
+ (item) => typeof item === "object" && item !== null && "type" in item && "name" in item && "domain" in item && "location" in item
28200
+ );
28201
+ }
28202
+ function parseJsonFile(filePath) {
28203
+ try {
28204
+ return JSON.parse(readFileSync3(filePath, "utf-8"));
28205
+ } catch {
28206
+ throw new DraftComponentLoadError(`Enrich file contains invalid JSON: ${filePath}`);
28207
+ }
28208
+ }
28209
+ function loadDraftComponentsFromFile(filePath) {
28210
+ if (!existsSync3(filePath)) {
28211
+ throw new DraftComponentLoadError(`Enrich file not found: ${filePath}`);
28212
+ }
28213
+ const parsed = parseJsonFile(filePath);
28214
+ if (!isDraftComponentArray(parsed)) {
28215
+ throw new DraftComponentLoadError(
28216
+ `Enrich file does not contain valid draft components: ${filePath}`
28217
+ );
28218
+ }
28219
+ return parsed;
28220
+ }
28221
+
27853
28222
  // src/features/extract/entrypoint/extract.ts
27854
28223
  function compareByCodePoint2(a, b) {
27855
28224
  if (a < b) return -1;
@@ -27909,87 +28278,145 @@ function tryExpandModuleRefs(data, configDir) {
27909
28278
  };
27910
28279
  }
27911
28280
  }
27912
- function createExtractCommand() {
27913
- return new Command21("extract").description("Extract architectural components from source code").requiredOption("--config <path>", "Path to extraction config file").option("--dry-run", "Show component counts per domain without full output").action((options) => {
27914
- if (!existsSync3(options.config)) {
27915
- console.log(
27916
- JSON.stringify(
27917
- formatError2("CONFIG_NOT_FOUND" /* ConfigNotFound */, `Config file not found: ${options.config}`)
27918
- )
27919
- );
27920
- process.exit(1);
27921
- }
27922
- const content = readFileSync3(options.config, "utf-8");
27923
- const parseResult = parseConfigFile(content);
27924
- if (!parseResult.success) {
27925
- console.log(
27926
- JSON.stringify(
27927
- formatError2("VALIDATION_ERROR" /* ValidationError */, `Invalid config file: ${parseResult.error}`)
27928
- )
27929
- );
27930
- process.exit(1);
27931
- }
27932
- const configDir = dirname3(resolve3(options.config));
27933
- const expansionResult = tryExpandModuleRefs(parseResult.data, configDir);
27934
- if (!expansionResult.success) {
28281
+ function outputResult(data, options) {
28282
+ if (options.output !== void 0) {
28283
+ try {
28284
+ writeFileSync(options.output, JSON.stringify(data));
28285
+ } catch {
27935
28286
  console.log(
27936
28287
  JSON.stringify(
27937
28288
  formatError2(
27938
28289
  "VALIDATION_ERROR" /* ValidationError */,
27939
- `Error expanding module references: ${expansionResult.error}`
28290
+ "Failed to write output file: " + options.output
27940
28291
  )
27941
28292
  )
27942
28293
  );
27943
- process.exit(1);
28294
+ process.exit(3 /* RuntimeError */);
27944
28295
  }
27945
- const expandedData = expansionResult.data;
27946
- if (!isValidExtractionConfig(expandedData)) {
27947
- const validationResult = validateExtractionConfig(expandedData);
27948
- console.log(
27949
- JSON.stringify(
27950
- formatError2(
27951
- "VALIDATION_ERROR" /* ValidationError */,
27952
- `Invalid extraction config:
27953
- ${formatValidationErrors2(validationResult.errors)}`
27954
- )
27955
- )
27956
- );
27957
- process.exit(1);
27958
- }
27959
- const unresolvedConfig = expandedData;
27960
- const configLoader = createConfigLoader(configDir);
27961
- const resolvedConfig = resolveConfig(unresolvedConfig, configLoader);
27962
- const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve3(configDir, filePath));
27963
- if (sourceFilePaths.length === 0) {
27964
- const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
27965
- console.log(
27966
- JSON.stringify(
27967
- formatError2(
27968
- "VALIDATION_ERROR" /* ValidationError */,
27969
- `No files matched extraction patterns: ${patterns}
28296
+ return;
28297
+ }
28298
+ console.log(JSON.stringify(data));
28299
+ }
28300
+ function exitWithRuntimeError(message) {
28301
+ console.log(JSON.stringify(formatError2("VALIDATION_ERROR" /* ValidationError */, message)));
28302
+ process.exit(3 /* RuntimeError */);
28303
+ }
28304
+ function exitWithConfigValidation(code, message) {
28305
+ console.log(JSON.stringify(formatError2(code, message)));
28306
+ process.exit(2 /* ConfigValidation */);
28307
+ }
28308
+ function exitWithExtractionFailure(fieldNames) {
28309
+ const uniqueFields = [...new Set(fieldNames)];
28310
+ console.log(
28311
+ JSON.stringify(
28312
+ formatError2(
28313
+ "VALIDATION_ERROR" /* ValidationError */,
28314
+ `Extraction failed for fields: ${uniqueFields.join(", ")}`
28315
+ )
28316
+ )
28317
+ );
28318
+ process.exit(1 /* ExtractionFailure */);
28319
+ }
28320
+ function resolveSourceFiles(resolvedConfig, configDir) {
28321
+ const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve3(configDir, filePath));
28322
+ if (sourceFilePaths.length === 0) {
28323
+ const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
28324
+ exitWithConfigValidation(
28325
+ "VALIDATION_ERROR" /* ValidationError */,
28326
+ `No files matched extraction patterns: ${patterns}
27970
28327
  Config directory: ${configDir}`
27971
- )
27972
- )
28328
+ );
28329
+ }
28330
+ return sourceFilePaths;
28331
+ }
28332
+ function loadAndValidateConfig(configPath) {
28333
+ if (!existsSync4(configPath)) {
28334
+ exitWithConfigValidation("CONFIG_NOT_FOUND" /* ConfigNotFound */, `Config file not found: ${configPath}`);
28335
+ }
28336
+ const content = readFileSync4(configPath, "utf-8");
28337
+ const parseResult = parseConfigFile(content);
28338
+ if (!parseResult.success) {
28339
+ exitWithConfigValidation(
28340
+ "VALIDATION_ERROR" /* ValidationError */,
28341
+ `Invalid config file: ${parseResult.error}`
28342
+ );
28343
+ }
28344
+ const configDir = dirname3(resolve3(configPath));
28345
+ const expansionResult = tryExpandModuleRefs(parseResult.data, configDir);
28346
+ if (!expansionResult.success) {
28347
+ exitWithConfigValidation(
28348
+ "VALIDATION_ERROR" /* ValidationError */,
28349
+ `Error expanding module references: ${expansionResult.error}`
28350
+ );
28351
+ }
28352
+ if (!isValidExtractionConfig(expansionResult.data)) {
28353
+ const validationResult = validateExtractionConfig(expansionResult.data);
28354
+ exitWithConfigValidation(
28355
+ "VALIDATION_ERROR" /* ValidationError */,
28356
+ `Invalid extraction config:
28357
+ ${formatValidationErrors2(validationResult.errors)}`
28358
+ );
28359
+ }
28360
+ return {
28361
+ resolvedConfig: resolveConfig(expansionResult.data, createConfigLoader(configDir)),
28362
+ configDir
28363
+ };
28364
+ }
28365
+ function createExtractCommand() {
28366
+ return new Command21("extract").description("Extract architectural components from source code").requiredOption("--config <path>", "Path to extraction config file").option("--dry-run", "Show component counts per domain without full output").option("-o, --output <file>", "Write output to file instead of stdout").option("--components-only", "Output only component identity (no metadata enrichment)").option("--enrich <file>", "Read draft components from file and enrich with extraction rules").option("--allow-incomplete", "Output components even when some extraction fields fail").action((options) => {
28367
+ if (options.componentsOnly && options.enrich !== void 0) {
28368
+ exitWithConfigValidation(
28369
+ "VALIDATION_ERROR" /* ValidationError */,
28370
+ "--components-only and --enrich cannot be used together"
27973
28371
  );
27974
- process.exit(1);
27975
28372
  }
27976
- const project = new Project();
27977
- project.addSourceFilesAtPaths(sourceFilePaths);
27978
- const components = extractComponents(
27979
- project,
27980
- sourceFilePaths,
28373
+ const {
27981
28374
  resolvedConfig,
27982
- matchesGlob,
27983
28375
  configDir
27984
- );
28376
+ } = loadAndValidateConfig(options.config);
28377
+ const sourceFilePaths = resolveSourceFiles(resolvedConfig, configDir);
28378
+ const project = new Project();
28379
+ project.addSourceFilesAtPaths(sourceFilePaths);
28380
+ const draftComponents = (() => {
28381
+ if (options.enrich === void 0) {
28382
+ return extractComponents(project, sourceFilePaths, resolvedConfig, matchesGlob, configDir);
28383
+ }
28384
+ try {
28385
+ return loadDraftComponentsFromFile(options.enrich);
28386
+ } catch (error48) {
28387
+ if (error48 instanceof DraftComponentLoadError) {
28388
+ exitWithRuntimeError(error48.message);
28389
+ }
28390
+ throw error48;
28391
+ }
28392
+ })();
27985
28393
  if (options.dryRun) {
27986
- const lines = formatDryRunOutput(components);
28394
+ const lines = formatDryRunOutput(draftComponents);
27987
28395
  for (const line of lines) {
27988
28396
  console.log(line);
27989
28397
  }
27990
28398
  return;
27991
28399
  }
27992
- console.log(JSON.stringify(formatSuccess(components)));
28400
+ if (options.componentsOnly) {
28401
+ outputResult(formatSuccess(draftComponents), options);
28402
+ return;
28403
+ }
28404
+ const enrichmentResult = enrichComponents(
28405
+ draftComponents,
28406
+ resolvedConfig,
28407
+ project,
28408
+ matchesGlob,
28409
+ configDir
28410
+ );
28411
+ const hasFailures = enrichmentResult.failures.length > 0;
28412
+ if (hasFailures && options.allowIncomplete === true) {
28413
+ outputResult(formatSuccess(enrichmentResult.components), options);
28414
+ return;
28415
+ }
28416
+ if (hasFailures) {
28417
+ exitWithExtractionFailure(enrichmentResult.failures.map((f) => f.field));
28418
+ }
28419
+ outputResult(formatSuccess(enrichmentResult.components), options);
27993
28420
  });
27994
28421
  }
27995
28422
 
@@ -28005,7 +28432,7 @@ function parsePackageJson(pkg) {
28005
28432
  }
28006
28433
  function loadPackageJson() {
28007
28434
  if (true) {
28008
- return { version: "0.7.30" };
28435
+ return { version: "0.8.0" };
28009
28436
  }
28010
28437
  const require2 = createRequire2(import.meta.url);
28011
28438
  return parsePackageJson(require2("../../package.json"));
@@ -28056,9 +28483,13 @@ program.parseAsync().catch((error48) => {
28056
28483
  /* istanbul ignore if -- @preserve: unreachable with typed ResolvedExtractionConfig; defensive guard */
28057
28484
  /* istanbul ignore else -- @preserve: false branch is unreachable; FindTarget is exhaustive */
28058
28485
  /* istanbul ignore next -- @preserve: unreachable with valid FindTarget type; defensive fallback */
28486
+ /* istanbul ignore next -- @preserve: only fromProperty reaches here; defensive guard */
28487
+ /* istanbul ignore next -- @preserve: catch always receives Error instances from ExtractionError */
28059
28488
  /* v8 ignore next -- @preserve defensive for non-Error throws */
28060
28489
  /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
28061
28490
  /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
28062
28491
  /* v8 ignore next -- @preserve: yaml library always throws Error instances; defensive guard */
28063
28492
  /* v8 ignore next -- @preserve: error is always Error from yaml parser; defensive guard */
28493
+ /* v8 ignore start -- @preserve: called from DraftComponentLoadError catch; validation logic tested in draft-component-loader.spec.ts */
28494
+ /* v8 ignore start -- @preserve: DraftComponentLoadError handling; validation tested in draft-component-loader.spec.ts */
28064
28495
  /* v8 ignore start -- @preserve: dry-run path tested via CLI integration */
package/dist/index.js CHANGED
@@ -23534,6 +23534,12 @@ async function fileExists(path2) {
23534
23534
  }
23535
23535
 
23536
23536
  // src/platform/infra/cli-presentation/error-codes.ts
23537
+ var ExitCode = /* @__PURE__ */ ((ExitCode2) => {
23538
+ ExitCode2[ExitCode2["ExtractionFailure"] = 1] = "ExtractionFailure";
23539
+ ExitCode2[ExitCode2["ConfigValidation"] = 2] = "ConfigValidation";
23540
+ ExitCode2[ExitCode2["RuntimeError"] = 3] = "RuntimeError";
23541
+ return ExitCode2;
23542
+ })(ExitCode || {});
23537
23543
  var CliErrorCode = /* @__PURE__ */ ((CliErrorCode3) => {
23538
23544
  CliErrorCode3["GraphNotFound"] = "GRAPH_NOT_FOUND";
23539
23545
  CliErrorCode3["ComponentNotFound"] = "COMPONENT_NOT_FOUND";
@@ -25092,8 +25098,9 @@ Examples:
25092
25098
 
25093
25099
  // src/features/extract/entrypoint/extract.ts
25094
25100
  import {
25095
- existsSync as existsSync3,
25096
- readFileSync as readFileSync3
25101
+ existsSync as existsSync4,
25102
+ readFileSync as readFileSync4,
25103
+ writeFileSync
25097
25104
  } from "node:fs";
25098
25105
  import {
25099
25106
  dirname as dirname3,
@@ -26170,14 +26177,192 @@ function requireRule(rule, ruleName, moduleName) {
26170
26177
  return rule;
26171
26178
  }
26172
26179
 
26180
+ // ../riviere-extract-ts/dist/platform/domain/string-transforms/transforms.js
26181
+ function stripSuffix(value, suffix) {
26182
+ if (value.endsWith(suffix)) {
26183
+ return value.slice(0, -suffix.length);
26184
+ }
26185
+ return value;
26186
+ }
26187
+ function stripPrefix(value, prefix) {
26188
+ if (value.startsWith(prefix)) {
26189
+ return value.slice(prefix.length);
26190
+ }
26191
+ return value;
26192
+ }
26193
+ function toLowerCase(value) {
26194
+ return value.toLowerCase();
26195
+ }
26196
+ function toUpperCase(value) {
26197
+ return value.toUpperCase();
26198
+ }
26199
+ function kebabToPascal(value) {
26200
+ return value.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
26201
+ }
26202
+ function pascalToKebab(value) {
26203
+ const transformed = value.replaceAll(/([A-Z])/g, "-$1").toLowerCase();
26204
+ return transformed.startsWith("-") ? transformed.slice(1) : transformed;
26205
+ }
26206
+ function applyTransforms(value, transform2) {
26207
+ const transformers = [];
26208
+ if (transform2.stripSuffix !== void 0) {
26209
+ const suffix = transform2.stripSuffix;
26210
+ transformers.push((v) => stripSuffix(v, suffix));
26211
+ }
26212
+ if (transform2.stripPrefix !== void 0) {
26213
+ const prefix = transform2.stripPrefix;
26214
+ transformers.push((v) => stripPrefix(v, prefix));
26215
+ }
26216
+ if (transform2.toLowerCase === true) {
26217
+ transformers.push(toLowerCase);
26218
+ }
26219
+ if (transform2.toUpperCase === true) {
26220
+ transformers.push(toUpperCase);
26221
+ }
26222
+ if (transform2.kebabToPascal === true) {
26223
+ transformers.push(kebabToPascal);
26224
+ }
26225
+ if (transform2.pascalToKebab === true) {
26226
+ transformers.push(pascalToKebab);
26227
+ }
26228
+ return transformers.reduce((acc, fn) => fn(acc), value);
26229
+ }
26230
+
26173
26231
  // ../riviere-extract-ts/dist/platform/domain/ast-literals/literal-detection.js
26174
26232
  import { SyntaxKind } from "ts-morph";
26233
+ var ExtractionError = class extends Error {
26234
+ location;
26235
+ constructor(message, file2, line) {
26236
+ super(`${message} at ${file2}:${line}`);
26237
+ this.name = "ExtractionError";
26238
+ this.location = {
26239
+ file: file2,
26240
+ line
26241
+ };
26242
+ }
26243
+ };
26244
+ function extractString(expression) {
26245
+ return expression.asKindOrThrow(SyntaxKind.StringLiteral).getLiteralValue();
26246
+ }
26247
+ function extractNumber(expression) {
26248
+ return Number(expression.getText());
26249
+ }
26250
+ function buildExtractionResult(expression) {
26251
+ const syntaxKind = expression.getKind();
26252
+ switch (syntaxKind) {
26253
+ case SyntaxKind.StringLiteral:
26254
+ return {
26255
+ kind: "string",
26256
+ value: extractString(expression)
26257
+ };
26258
+ case SyntaxKind.NumericLiteral:
26259
+ return {
26260
+ kind: "number",
26261
+ value: extractNumber(expression)
26262
+ };
26263
+ case SyntaxKind.TrueKeyword:
26264
+ return {
26265
+ kind: "boolean",
26266
+ value: true
26267
+ };
26268
+ case SyntaxKind.FalseKeyword:
26269
+ return {
26270
+ kind: "boolean",
26271
+ value: false
26272
+ };
26273
+ default:
26274
+ return void 0;
26275
+ }
26276
+ }
26277
+ function throwMissingInitializer(file2, line) {
26278
+ throw new ExtractionError("No initializer found", file2, line);
26279
+ }
26280
+ function throwNonLiteralValue(expression, file2, line) {
26281
+ throw new ExtractionError(`Non-literal value detected (${expression.getKindName()}): ${expression.getText()}. Only inline literals (strings, numbers, booleans) are supported`, file2, line);
26282
+ }
26283
+ function extractLiteralValue(expression, file2, line) {
26284
+ if (expression === void 0) {
26285
+ throwMissingInitializer(file2, line);
26286
+ }
26287
+ const result = buildExtractionResult(expression);
26288
+ if (result === void 0) {
26289
+ throwNonLiteralValue(expression, file2, line);
26290
+ }
26291
+ return result;
26292
+ }
26175
26293
 
26176
26294
  // ../riviere-extract-ts/dist/domain/value-extraction/evaluate-extraction-rule-generic.js
26177
26295
  import { SyntaxKind as SyntaxKind2 } from "ts-morph";
26178
26296
 
26179
26297
  // ../riviere-extract-ts/dist/domain/value-extraction/evaluate-extraction-rule.js
26180
26298
  import { SyntaxKind as SyntaxKind3 } from "ts-morph";
26299
+ function literal2(value) {
26300
+ return { value };
26301
+ }
26302
+ function evaluateLiteralRule(rule) {
26303
+ return literal2(rule.literal);
26304
+ }
26305
+ function evaluateFromClassNameRule(rule, classDecl) {
26306
+ const className = classDecl.getName() ?? "";
26307
+ if (rule.fromClassName === true) {
26308
+ return { value: className };
26309
+ }
26310
+ const transform2 = rule.fromClassName.transform;
26311
+ if (transform2 === void 0) {
26312
+ return { value: className };
26313
+ }
26314
+ return { value: applyTransforms(className, transform2) };
26315
+ }
26316
+ function evaluateFromFilePathRule(rule, filePath) {
26317
+ const { pattern, capture, transform: transform2 } = rule.fromFilePath;
26318
+ const regex = new RegExp(pattern);
26319
+ const match2 = regex.exec(filePath);
26320
+ if (match2 === null) {
26321
+ throw new ExtractionError(`Pattern '${pattern}' did not match file path '${filePath}'`, filePath, 0);
26322
+ }
26323
+ const capturedValue = match2[capture];
26324
+ if (capturedValue === void 0) {
26325
+ throw new ExtractionError(`Capture group ${capture} out of bounds. Pattern has ${match2.length - 1} capture groups`, filePath, 0);
26326
+ }
26327
+ if (transform2 === void 0) {
26328
+ return { value: capturedValue };
26329
+ }
26330
+ return { value: applyTransforms(capturedValue, transform2) };
26331
+ }
26332
+ function findPropertyInHierarchy(classDecl, propertyName, isStatic) {
26333
+ const properties = isStatic ? classDecl.getStaticProperties() : classDecl.getInstanceProperties();
26334
+ const property = properties.find((p) => p.getName() === propertyName);
26335
+ if (property !== void 0 && "getInitializer" in property) {
26336
+ const sourceFile = classDecl.getSourceFile();
26337
+ return {
26338
+ initializer: property.getInitializer(),
26339
+ filePath: sourceFile.getFilePath(),
26340
+ line: property.getStartLineNumber()
26341
+ };
26342
+ }
26343
+ const baseClass = classDecl.getBaseClass();
26344
+ if (baseClass === void 0) {
26345
+ return void 0;
26346
+ }
26347
+ return findPropertyInHierarchy(baseClass, propertyName, isStatic);
26348
+ }
26349
+ function evaluateFromPropertyRule(rule, classDecl) {
26350
+ const { name, kind, transform: transform2 } = rule.fromProperty;
26351
+ const isStatic = kind === "static";
26352
+ const propertyInfo = findPropertyInHierarchy(classDecl, name, isStatic);
26353
+ if (propertyInfo === void 0) {
26354
+ const sourceFile = classDecl.getSourceFile();
26355
+ throw new ExtractionError(`Property '${name}' not found on class '${classDecl.getName() ?? "anonymous"}'`, sourceFile.getFilePath(), classDecl.getStartLineNumber());
26356
+ }
26357
+ const literalResult = extractLiteralValue(propertyInfo.initializer, propertyInfo.filePath, propertyInfo.line);
26358
+ if (transform2 === void 0) {
26359
+ return { value: literalResult.value };
26360
+ }
26361
+ if (typeof literalResult.value !== "string") {
26362
+ return { value: literalResult.value };
26363
+ }
26364
+ return { value: applyTransforms(literalResult.value, transform2) };
26365
+ }
26181
26366
 
26182
26367
  // ../../node_modules/.pnpm/@isaacs+balanced-match@4.0.1/node_modules/@isaacs/balanced-match/dist/esm/index.js
26183
26368
  var balanced = (a, b, str) => {
@@ -27736,6 +27921,158 @@ function matchesGlob(path2, pattern) {
27736
27921
  return minimatch(path2, pattern);
27737
27922
  }
27738
27923
 
27924
+ // ../riviere-extract-ts/dist/domain/value-extraction/enrich-components.js
27925
+ import { posix as posix2 } from "node:path";
27926
+ function findMatchingModule2(filePath, modules, globMatcher, configDir) {
27927
+ const normalized = filePath.replaceAll(/\\+/g, "/");
27928
+ const pathToMatch = posix2.relative(configDir.replaceAll(/\\+/g, "/"), normalized);
27929
+ return modules.find((m) => globMatcher(pathToMatch, m.path));
27930
+ }
27931
+ function isDetectionRule2(rule) {
27932
+ if (typeof rule !== "object" || rule === null) {
27933
+ return false;
27934
+ }
27935
+ return "find" in rule && "where" in rule;
27936
+ }
27937
+ function getBuiltInRule(module, componentType) {
27938
+ const ruleMap = {
27939
+ api: module.api,
27940
+ useCase: module.useCase,
27941
+ domainOp: module.domainOp,
27942
+ event: module.event,
27943
+ eventHandler: module.eventHandler,
27944
+ ui: module.ui
27945
+ };
27946
+ const rule = ruleMap[componentType];
27947
+ if (isDetectionRule2(rule)) {
27948
+ return rule;
27949
+ }
27950
+ return void 0;
27951
+ }
27952
+ function findDetectionRule(module, componentType) {
27953
+ const builtInTypes = [
27954
+ "api",
27955
+ "useCase",
27956
+ "domainOp",
27957
+ "event",
27958
+ "eventHandler",
27959
+ "ui"
27960
+ ];
27961
+ if (builtInTypes.includes(componentType)) {
27962
+ return getBuiltInRule(module, componentType);
27963
+ }
27964
+ return module.customTypes?.[componentType];
27965
+ }
27966
+ function isLiteralRule(rule) {
27967
+ return "literal" in rule;
27968
+ }
27969
+ function isFromClassNameRule(rule) {
27970
+ return "fromClassName" in rule;
27971
+ }
27972
+ function isFromFilePathRule(rule) {
27973
+ return "fromFilePath" in rule;
27974
+ }
27975
+ function isFromPropertyRule(rule) {
27976
+ return "fromProperty" in rule;
27977
+ }
27978
+ function findClassAtLine(project, draft) {
27979
+ const sourceFile = project.getSourceFile(draft.location.file);
27980
+ if (sourceFile === void 0) {
27981
+ throw new ExtractionError(`Source file '${draft.location.file}' not found in project`, draft.location.file, draft.location.line);
27982
+ }
27983
+ const classDecl = sourceFile.getClasses().find((c) => c.getStartLineNumber() === draft.location.line);
27984
+ if (classDecl === void 0) {
27985
+ throw new ExtractionError(`No class declaration found at line ${draft.location.line}`, draft.location.file, draft.location.line);
27986
+ }
27987
+ return classDecl;
27988
+ }
27989
+ function evaluateClassRule(rule, classDecl) {
27990
+ if (isFromClassNameRule(rule)) {
27991
+ return evaluateFromClassNameRule(rule, classDecl);
27992
+ }
27993
+ if (!isFromPropertyRule(rule)) {
27994
+ throw new ExtractionError("Unsupported extraction rule type for class-based component", classDecl.getSourceFile().getFilePath(), classDecl.getStartLineNumber());
27995
+ }
27996
+ return evaluateFromPropertyRule(rule, classDecl);
27997
+ }
27998
+ function evaluateRule(rule, draft, project) {
27999
+ if (isLiteralRule(rule)) {
28000
+ return evaluateLiteralRule(rule);
28001
+ }
28002
+ if (isFromFilePathRule(rule)) {
28003
+ return evaluateFromFilePathRule(rule, draft.location.file);
28004
+ }
28005
+ const classDecl = findClassAtLine(project, draft);
28006
+ return evaluateClassRule(rule, classDecl);
28007
+ }
28008
+ function componentWithEmptyMetadata(draft) {
28009
+ return {
28010
+ enriched: {
28011
+ ...draft,
28012
+ metadata: {}
28013
+ },
28014
+ failures: []
28015
+ };
28016
+ }
28017
+ function extractMetadataFields(extractBlock, draft, project) {
28018
+ const metadata = {};
28019
+ const missing = [];
28020
+ const failures = [];
28021
+ for (const [fieldName, extractionRule] of Object.entries(extractBlock)) {
28022
+ try {
28023
+ metadata[fieldName] = evaluateRule(extractionRule, draft, project).value;
28024
+ } catch (error48) {
28025
+ const errorMessage = error48 instanceof Error ? error48.message : String(error48);
28026
+ failures.push({
28027
+ component: draft,
28028
+ field: fieldName,
28029
+ error: errorMessage
28030
+ });
28031
+ missing.push(fieldName);
28032
+ }
28033
+ }
28034
+ return {
28035
+ metadata,
28036
+ missing,
28037
+ failures
28038
+ };
28039
+ }
28040
+ function enrichSingleComponent(draft, config2, project, globMatcher, configDir) {
28041
+ const module = findMatchingModule2(draft.location.file, config2.modules, globMatcher, configDir);
28042
+ if (module === void 0) {
28043
+ return componentWithEmptyMetadata(draft);
28044
+ }
28045
+ const detectionRule = findDetectionRule(module, draft.type);
28046
+ if (detectionRule?.extract === void 0) {
28047
+ return componentWithEmptyMetadata(draft);
28048
+ }
28049
+ const extracted = extractMetadataFields(detectionRule.extract, draft, project);
28050
+ const enriched = {
28051
+ ...draft,
28052
+ metadata: extracted.metadata
28053
+ };
28054
+ if (extracted.missing.length > 0) {
28055
+ enriched._missing = extracted.missing;
28056
+ }
28057
+ return {
28058
+ enriched,
28059
+ failures: extracted.failures
28060
+ };
28061
+ }
28062
+ function enrichComponents(draftComponents, config2, project, globMatcher, configDir) {
28063
+ const allComponents = [];
28064
+ const allFailures = [];
28065
+ for (const draft of draftComponents) {
28066
+ const result = enrichSingleComponent(draft, config2, project, globMatcher, configDir);
28067
+ allComponents.push(result.enriched);
28068
+ allFailures.push(...result.failures);
28069
+ }
28070
+ return {
28071
+ components: allComponents,
28072
+ failures: allFailures
28073
+ };
28074
+ }
28075
+
27739
28076
  // src/features/extract/commands/config-loader.ts
27740
28077
  import {
27741
28078
  dirname as dirname2,
@@ -27867,6 +28204,44 @@ function expandModuleRefs(config2, configDir) {
27867
28204
  };
27868
28205
  }
27869
28206
 
28207
+ // src/features/extract/commands/draft-component-loader.ts
28208
+ import {
28209
+ existsSync as existsSync3,
28210
+ readFileSync as readFileSync3
28211
+ } from "node:fs";
28212
+ var DraftComponentLoadError = class extends Error {
28213
+ constructor(message) {
28214
+ super(message);
28215
+ this.name = "DraftComponentLoadError";
28216
+ Error.captureStackTrace?.(this, this.constructor);
28217
+ }
28218
+ };
28219
+ function isDraftComponentArray(value) {
28220
+ if (!Array.isArray(value)) return false;
28221
+ return value.every(
28222
+ (item) => typeof item === "object" && item !== null && "type" in item && "name" in item && "domain" in item && "location" in item
28223
+ );
28224
+ }
28225
+ function parseJsonFile(filePath) {
28226
+ try {
28227
+ return JSON.parse(readFileSync3(filePath, "utf-8"));
28228
+ } catch {
28229
+ throw new DraftComponentLoadError(`Enrich file contains invalid JSON: ${filePath}`);
28230
+ }
28231
+ }
28232
+ function loadDraftComponentsFromFile(filePath) {
28233
+ if (!existsSync3(filePath)) {
28234
+ throw new DraftComponentLoadError(`Enrich file not found: ${filePath}`);
28235
+ }
28236
+ const parsed = parseJsonFile(filePath);
28237
+ if (!isDraftComponentArray(parsed)) {
28238
+ throw new DraftComponentLoadError(
28239
+ `Enrich file does not contain valid draft components: ${filePath}`
28240
+ );
28241
+ }
28242
+ return parsed;
28243
+ }
28244
+
27870
28245
  // src/features/extract/entrypoint/extract.ts
27871
28246
  function compareByCodePoint2(a, b) {
27872
28247
  if (a < b) return -1;
@@ -27926,87 +28301,145 @@ function tryExpandModuleRefs(data, configDir) {
27926
28301
  };
27927
28302
  }
27928
28303
  }
27929
- function createExtractCommand() {
27930
- return new Command21("extract").description("Extract architectural components from source code").requiredOption("--config <path>", "Path to extraction config file").option("--dry-run", "Show component counts per domain without full output").action((options) => {
27931
- if (!existsSync3(options.config)) {
27932
- console.log(
27933
- JSON.stringify(
27934
- formatError2("CONFIG_NOT_FOUND" /* ConfigNotFound */, `Config file not found: ${options.config}`)
27935
- )
27936
- );
27937
- process.exit(1);
27938
- }
27939
- const content = readFileSync3(options.config, "utf-8");
27940
- const parseResult = parseConfigFile(content);
27941
- if (!parseResult.success) {
27942
- console.log(
27943
- JSON.stringify(
27944
- formatError2("VALIDATION_ERROR" /* ValidationError */, `Invalid config file: ${parseResult.error}`)
27945
- )
27946
- );
27947
- process.exit(1);
27948
- }
27949
- const configDir = dirname3(resolve3(options.config));
27950
- const expansionResult = tryExpandModuleRefs(parseResult.data, configDir);
27951
- if (!expansionResult.success) {
28304
+ function outputResult(data, options) {
28305
+ if (options.output !== void 0) {
28306
+ try {
28307
+ writeFileSync(options.output, JSON.stringify(data));
28308
+ } catch {
27952
28309
  console.log(
27953
28310
  JSON.stringify(
27954
28311
  formatError2(
27955
28312
  "VALIDATION_ERROR" /* ValidationError */,
27956
- `Error expanding module references: ${expansionResult.error}`
28313
+ "Failed to write output file: " + options.output
27957
28314
  )
27958
28315
  )
27959
28316
  );
27960
- process.exit(1);
28317
+ process.exit(3 /* RuntimeError */);
27961
28318
  }
27962
- const expandedData = expansionResult.data;
27963
- if (!isValidExtractionConfig(expandedData)) {
27964
- const validationResult = validateExtractionConfig(expandedData);
27965
- console.log(
27966
- JSON.stringify(
27967
- formatError2(
27968
- "VALIDATION_ERROR" /* ValidationError */,
27969
- `Invalid extraction config:
27970
- ${formatValidationErrors2(validationResult.errors)}`
27971
- )
27972
- )
27973
- );
27974
- process.exit(1);
27975
- }
27976
- const unresolvedConfig = expandedData;
27977
- const configLoader = createConfigLoader(configDir);
27978
- const resolvedConfig = resolveConfig(unresolvedConfig, configLoader);
27979
- const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve3(configDir, filePath));
27980
- if (sourceFilePaths.length === 0) {
27981
- const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
27982
- console.log(
27983
- JSON.stringify(
27984
- formatError2(
27985
- "VALIDATION_ERROR" /* ValidationError */,
27986
- `No files matched extraction patterns: ${patterns}
28319
+ return;
28320
+ }
28321
+ console.log(JSON.stringify(data));
28322
+ }
28323
+ function exitWithRuntimeError(message) {
28324
+ console.log(JSON.stringify(formatError2("VALIDATION_ERROR" /* ValidationError */, message)));
28325
+ process.exit(3 /* RuntimeError */);
28326
+ }
28327
+ function exitWithConfigValidation(code, message) {
28328
+ console.log(JSON.stringify(formatError2(code, message)));
28329
+ process.exit(2 /* ConfigValidation */);
28330
+ }
28331
+ function exitWithExtractionFailure(fieldNames) {
28332
+ const uniqueFields = [...new Set(fieldNames)];
28333
+ console.log(
28334
+ JSON.stringify(
28335
+ formatError2(
28336
+ "VALIDATION_ERROR" /* ValidationError */,
28337
+ `Extraction failed for fields: ${uniqueFields.join(", ")}`
28338
+ )
28339
+ )
28340
+ );
28341
+ process.exit(1 /* ExtractionFailure */);
28342
+ }
28343
+ function resolveSourceFiles(resolvedConfig, configDir) {
28344
+ const sourceFilePaths = resolvedConfig.modules.flatMap((module) => globSync(module.path, { cwd: configDir })).map((filePath) => resolve3(configDir, filePath));
28345
+ if (sourceFilePaths.length === 0) {
28346
+ const patterns = resolvedConfig.modules.map((m) => m.path).join(", ");
28347
+ exitWithConfigValidation(
28348
+ "VALIDATION_ERROR" /* ValidationError */,
28349
+ `No files matched extraction patterns: ${patterns}
27987
28350
  Config directory: ${configDir}`
27988
- )
27989
- )
28351
+ );
28352
+ }
28353
+ return sourceFilePaths;
28354
+ }
28355
+ function loadAndValidateConfig(configPath) {
28356
+ if (!existsSync4(configPath)) {
28357
+ exitWithConfigValidation("CONFIG_NOT_FOUND" /* ConfigNotFound */, `Config file not found: ${configPath}`);
28358
+ }
28359
+ const content = readFileSync4(configPath, "utf-8");
28360
+ const parseResult = parseConfigFile(content);
28361
+ if (!parseResult.success) {
28362
+ exitWithConfigValidation(
28363
+ "VALIDATION_ERROR" /* ValidationError */,
28364
+ `Invalid config file: ${parseResult.error}`
28365
+ );
28366
+ }
28367
+ const configDir = dirname3(resolve3(configPath));
28368
+ const expansionResult = tryExpandModuleRefs(parseResult.data, configDir);
28369
+ if (!expansionResult.success) {
28370
+ exitWithConfigValidation(
28371
+ "VALIDATION_ERROR" /* ValidationError */,
28372
+ `Error expanding module references: ${expansionResult.error}`
28373
+ );
28374
+ }
28375
+ if (!isValidExtractionConfig(expansionResult.data)) {
28376
+ const validationResult = validateExtractionConfig(expansionResult.data);
28377
+ exitWithConfigValidation(
28378
+ "VALIDATION_ERROR" /* ValidationError */,
28379
+ `Invalid extraction config:
28380
+ ${formatValidationErrors2(validationResult.errors)}`
28381
+ );
28382
+ }
28383
+ return {
28384
+ resolvedConfig: resolveConfig(expansionResult.data, createConfigLoader(configDir)),
28385
+ configDir
28386
+ };
28387
+ }
28388
+ function createExtractCommand() {
28389
+ return new Command21("extract").description("Extract architectural components from source code").requiredOption("--config <path>", "Path to extraction config file").option("--dry-run", "Show component counts per domain without full output").option("-o, --output <file>", "Write output to file instead of stdout").option("--components-only", "Output only component identity (no metadata enrichment)").option("--enrich <file>", "Read draft components from file and enrich with extraction rules").option("--allow-incomplete", "Output components even when some extraction fields fail").action((options) => {
28390
+ if (options.componentsOnly && options.enrich !== void 0) {
28391
+ exitWithConfigValidation(
28392
+ "VALIDATION_ERROR" /* ValidationError */,
28393
+ "--components-only and --enrich cannot be used together"
27990
28394
  );
27991
- process.exit(1);
27992
28395
  }
27993
- const project = new Project();
27994
- project.addSourceFilesAtPaths(sourceFilePaths);
27995
- const components = extractComponents(
27996
- project,
27997
- sourceFilePaths,
28396
+ const {
27998
28397
  resolvedConfig,
27999
- matchesGlob,
28000
28398
  configDir
28001
- );
28399
+ } = loadAndValidateConfig(options.config);
28400
+ const sourceFilePaths = resolveSourceFiles(resolvedConfig, configDir);
28401
+ const project = new Project();
28402
+ project.addSourceFilesAtPaths(sourceFilePaths);
28403
+ const draftComponents = (() => {
28404
+ if (options.enrich === void 0) {
28405
+ return extractComponents(project, sourceFilePaths, resolvedConfig, matchesGlob, configDir);
28406
+ }
28407
+ try {
28408
+ return loadDraftComponentsFromFile(options.enrich);
28409
+ } catch (error48) {
28410
+ if (error48 instanceof DraftComponentLoadError) {
28411
+ exitWithRuntimeError(error48.message);
28412
+ }
28413
+ throw error48;
28414
+ }
28415
+ })();
28002
28416
  if (options.dryRun) {
28003
- const lines = formatDryRunOutput(components);
28417
+ const lines = formatDryRunOutput(draftComponents);
28004
28418
  for (const line of lines) {
28005
28419
  console.log(line);
28006
28420
  }
28007
28421
  return;
28008
28422
  }
28009
- console.log(JSON.stringify(formatSuccess(components)));
28423
+ if (options.componentsOnly) {
28424
+ outputResult(formatSuccess(draftComponents), options);
28425
+ return;
28426
+ }
28427
+ const enrichmentResult = enrichComponents(
28428
+ draftComponents,
28429
+ resolvedConfig,
28430
+ project,
28431
+ matchesGlob,
28432
+ configDir
28433
+ );
28434
+ const hasFailures = enrichmentResult.failures.length > 0;
28435
+ if (hasFailures && options.allowIncomplete === true) {
28436
+ outputResult(formatSuccess(enrichmentResult.components), options);
28437
+ return;
28438
+ }
28439
+ if (hasFailures) {
28440
+ exitWithExtractionFailure(enrichmentResult.failures.map((f) => f.field));
28441
+ }
28442
+ outputResult(formatSuccess(enrichmentResult.components), options);
28010
28443
  });
28011
28444
  }
28012
28445
 
@@ -28058,6 +28491,7 @@ function createProgram() {
28058
28491
  }
28059
28492
  export {
28060
28493
  CliErrorCode,
28494
+ ExitCode,
28061
28495
  createProgram,
28062
28496
  formatError2 as formatError,
28063
28497
  formatSuccess,
@@ -28073,9 +28507,13 @@ export {
28073
28507
  /* istanbul ignore if -- @preserve: unreachable with typed ResolvedExtractionConfig; defensive guard */
28074
28508
  /* istanbul ignore else -- @preserve: false branch is unreachable; FindTarget is exhaustive */
28075
28509
  /* istanbul ignore next -- @preserve: unreachable with valid FindTarget type; defensive fallback */
28510
+ /* istanbul ignore next -- @preserve: only fromProperty reaches here; defensive guard */
28511
+ /* istanbul ignore next -- @preserve: catch always receives Error instances from ExtractionError */
28076
28512
  /* v8 ignore next -- @preserve defensive for non-Error throws */
28077
28513
  /* v8 ignore start -- @preserve: trivial comparator, Map keys guarantee a !== b */
28078
28514
  /* v8 ignore start -- @preserve: dry-run output formatting; tested via CLI integration */
28079
28515
  /* v8 ignore next -- @preserve: yaml library always throws Error instances; defensive guard */
28080
28516
  /* v8 ignore next -- @preserve: error is always Error from yaml parser; defensive guard */
28517
+ /* v8 ignore start -- @preserve: called from DraftComponentLoadError catch; validation logic tested in draft-component-loader.spec.ts */
28518
+ /* v8 ignore start -- @preserve: DraftComponentLoadError handling; validation tested in draft-component-loader.spec.ts */
28081
28519
  /* v8 ignore start -- @preserve: dry-run path tested via CLI integration */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@living-architecture/riviere-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,10 +32,10 @@
32
32
  "glob": "^11.0.2",
33
33
  "ts-morph": "^24.0.0",
34
34
  "yaml": "^2.7.0",
35
- "@living-architecture/riviere-builder": "0.6.0",
36
- "@living-architecture/riviere-extract-config": "0.4.0",
37
- "@living-architecture/riviere-extract-ts": "0.2.0",
38
- "@living-architecture/riviere-schema": "0.5.0",
39
- "@living-architecture/riviere-query": "0.5.0"
35
+ "@living-architecture/riviere-builder": "0.6.1",
36
+ "@living-architecture/riviere-extract-config": "0.4.1",
37
+ "@living-architecture/riviere-extract-ts": "0.2.1",
38
+ "@living-architecture/riviere-query": "0.5.1",
39
+ "@living-architecture/riviere-schema": "0.5.1"
40
40
  }
41
41
  }