@xanahlight/component-forge 0.1.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/commands/check/index.d.ts +30 -0
- package/dist/commands/check/index.js +255 -0
- package/dist/commands/check/index.js.map +1 -0
- package/dist/commands/explain/index.d.ts +12 -0
- package/dist/commands/explain/index.js +37 -0
- package/dist/commands/explain/index.js.map +1 -0
- package/dist/commands/explain/topics.d.ts +3 -0
- package/dist/commands/explain/topics.js +138 -0
- package/dist/commands/explain/topics.js.map +1 -0
- package/dist/commands/{generate.d.ts → generate/index.d.ts} +1 -1
- package/dist/commands/{generate.js → generate/index.js} +21 -12
- package/dist/commands/generate/index.js.map +1 -0
- package/dist/commands/init/index.d.ts +29 -0
- package/dist/commands/init/index.js +157 -0
- package/dist/commands/init/index.js.map +1 -0
- package/dist/commands/migrate/classifier.d.ts +22 -0
- package/dist/commands/migrate/classifier.js +48 -0
- package/dist/commands/migrate/classifier.js.map +1 -0
- package/dist/commands/migrate/index.d.ts +8 -0
- package/dist/commands/migrate/index.js +33 -0
- package/dist/commands/migrate/index.js.map +1 -0
- package/dist/commands/migrate/plan-builder.d.ts +27 -0
- package/dist/commands/migrate/plan-builder.js +106 -0
- package/dist/commands/migrate/plan-builder.js.map +1 -0
- package/dist/commands/migrate/printer.d.ts +6 -0
- package/dist/commands/migrate/printer.js +46 -0
- package/dist/commands/migrate/printer.js.map +1 -0
- package/dist/commands/validate/index.d.ts +26 -0
- package/dist/commands/{validate.js → validate/index.js} +8 -5
- package/dist/commands/validate/index.js.map +1 -0
- package/dist/index.js +106 -10
- package/dist/index.js.map +1 -1
- package/dist/shared/format.d.ts +26 -0
- package/dist/shared/format.js +40 -0
- package/dist/shared/format.js.map +1 -0
- package/dist/templates/files.d.ts +1 -1
- package/dist/templates/files.js +61 -9
- package/dist/templates/files.js.map +1 -1
- package/dist/types/folder-tree.d.ts +31 -2
- package/dist/types/folder-tree.js +18 -0
- package/dist/types/folder-tree.js.map +1 -1
- package/dist/utils/config.d.ts +19 -6
- package/dist/utils/config.js +76 -14
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/template-resolver.d.ts +1 -1
- package/dist/utils/template-resolver.js +1 -1
- package/dist/utils/template-resolver.js.map +1 -1
- package/package.json +14 -6
- package/dist/commands/generate.js.map +0 -1
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/init.js +0 -54
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/validate.d.ts +0 -1
- package/dist/commands/validate.js.map +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadProjectConfig = exports.CONFIG_FILENAMES = void 0;
|
|
7
|
+
exports.createStructure = createStructure;
|
|
8
|
+
exports.promptInitAnswers = promptInitAnswers;
|
|
9
|
+
exports.runInit = runInit;
|
|
10
|
+
exports.initCommand = initCommand;
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
13
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
14
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
15
|
+
const fsd_1 = require("../../templates/fsd");
|
|
16
|
+
const modular_1 = require("../../templates/modular");
|
|
17
|
+
const config_1 = require("../../utils/config");
|
|
18
|
+
Object.defineProperty(exports, "CONFIG_FILENAMES", { enumerable: true, get: function () { return config_1.CONFIG_FILENAMES; } });
|
|
19
|
+
Object.defineProperty(exports, "loadProjectConfig", { enumerable: true, get: function () { return config_1.loadProjectConfig; } });
|
|
20
|
+
const logger_1 = require("../../utils/logger");
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Template registry
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const templates = {
|
|
25
|
+
fsd: fsd_1.fsdTemplate,
|
|
26
|
+
modular: modular_1.modularTemplate,
|
|
27
|
+
};
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Architecture descriptions shown in the interactive selector
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const ARCHITECTURE_DESCRIPTIONS = {
|
|
32
|
+
fsd: 'Feature-Sliced Design — layer-based architecture for large apps',
|
|
33
|
+
modular: 'Modular — module-centric architecture for medium-sized apps',
|
|
34
|
+
};
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Folder structure creation
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* Recursively creates folder structure from a FolderTree definition.
|
|
40
|
+
* Returns the list of created paths (relative to cwd) for summary output.
|
|
41
|
+
*/
|
|
42
|
+
function createStructure(tree, basePath, cwd) {
|
|
43
|
+
const created = [];
|
|
44
|
+
for (const [folderName, children] of Object.entries(tree)) {
|
|
45
|
+
const folderPath = node_path_1.default.join(basePath, folderName);
|
|
46
|
+
fs_extra_1.default.ensureDirSync(folderPath);
|
|
47
|
+
created.push(node_path_1.default.relative(cwd, folderPath));
|
|
48
|
+
created.push(...createStructure(children, folderPath, cwd));
|
|
49
|
+
}
|
|
50
|
+
return created;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Runs interactive prompts to collect init parameters.
|
|
54
|
+
* Throws if the user cancels (Ctrl+C) — caller handles the exit.
|
|
55
|
+
*/
|
|
56
|
+
async function promptInitAnswers() {
|
|
57
|
+
console.log(chalk_1.default.bold('\n component-forge — project initialisation\n'));
|
|
58
|
+
const architecture = await (0, prompts_1.select)({
|
|
59
|
+
message: 'Choose your architecture:',
|
|
60
|
+
choices: [
|
|
61
|
+
{
|
|
62
|
+
name: `${chalk_1.default.cyan('FSD')} ${chalk_1.default.gray(ARCHITECTURE_DESCRIPTIONS.fsd)}`,
|
|
63
|
+
value: 'fsd',
|
|
64
|
+
short: 'FSD',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: `${chalk_1.default.cyan('Modular')} ${chalk_1.default.gray(ARCHITECTURE_DESCRIPTIONS.modular)}`,
|
|
68
|
+
value: 'modular',
|
|
69
|
+
short: 'Modular',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
const srcDir = await (0, prompts_1.input)({
|
|
74
|
+
message: 'Source directory:',
|
|
75
|
+
default: 'src',
|
|
76
|
+
validate: (value) => {
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
if (!trimmed)
|
|
79
|
+
return 'Source directory cannot be empty';
|
|
80
|
+
if (/[<>:"|?*]/.test(trimmed))
|
|
81
|
+
return 'Directory name contains invalid characters';
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
transformer: (value) => chalk_1.default.cyan(value),
|
|
85
|
+
});
|
|
86
|
+
const confirmed = await (0, prompts_1.confirm)({
|
|
87
|
+
message: `Initialise ${chalk_1.default.bold(architecture.toUpperCase())} in ${chalk_1.default.bold(`./${srcDir.trim()}`)}?`,
|
|
88
|
+
default: true,
|
|
89
|
+
});
|
|
90
|
+
if (!confirmed) {
|
|
91
|
+
logger_1.logger.info('Aborted.');
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
return { architecture, srcDir: srcDir.trim() };
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Core init logic — pure, no side-effects on I/O apart from FS writes
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
/**
|
|
100
|
+
* Performs the actual init: creates folder structure + writes config.
|
|
101
|
+
* Exported so it can be called directly (non-interactive) or from tests.
|
|
102
|
+
*/
|
|
103
|
+
function runInit(architecture, srcDir, projectRoot) {
|
|
104
|
+
const configPath = node_path_1.default.join(projectRoot, config_1.CONFIG_FILENAMES.json);
|
|
105
|
+
if (fs_extra_1.default.existsSync(configPath)) {
|
|
106
|
+
logger_1.logger.error(`Project already initialised (${config_1.CONFIG_FILENAMES.json} exists).`);
|
|
107
|
+
logger_1.logger.info('Remove it or delete forge.config.ts if you want to reinitialise.');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
logger_1.logger.info(`\nInitialising ${chalk_1.default.bold(architecture.toUpperCase())} architecture in ${chalk_1.default.cyan(`./${srcDir}`)}…\n`);
|
|
111
|
+
const template = templates[architecture];
|
|
112
|
+
// Resolve the base path for structure (respects custom srcDir)
|
|
113
|
+
const basePath = node_path_1.default.join(projectRoot, srcDir);
|
|
114
|
+
const created = createStructure(template, basePath, projectRoot);
|
|
115
|
+
for (const p of created) {
|
|
116
|
+
logger_1.logger.success(`Created: ${p}`);
|
|
117
|
+
}
|
|
118
|
+
const config = { architecture, srcDir };
|
|
119
|
+
(0, config_1.writeProjectConfig)(config, projectRoot);
|
|
120
|
+
logger_1.logger.success(`\nCreated: ${config_1.CONFIG_FILENAMES.json}`);
|
|
121
|
+
logger_1.logger.info(`Tip: rename to forge.config.ts for TypeScript support and IntelliSense.`);
|
|
122
|
+
console.log(chalk_1.default.bold(`\n ✓ ${architecture.toUpperCase()} project initialised successfully!\n`));
|
|
123
|
+
console.log(chalk_1.default.gray(` Next steps:\n`) +
|
|
124
|
+
chalk_1.default.white(` component-forge generate feature <name>\n`) +
|
|
125
|
+
chalk_1.default.white(` component-forge validate\n`) +
|
|
126
|
+
chalk_1.default.white(` component-forge check\n`));
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Command entry point
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Called from index.ts.
|
|
133
|
+
* If architecture is provided — runs non-interactively (backwards-compatible).
|
|
134
|
+
* If omitted — runs interactive prompts.
|
|
135
|
+
*/
|
|
136
|
+
function initCommand(architecture) {
|
|
137
|
+
if (architecture) {
|
|
138
|
+
// Non-interactive path — keep full backwards compatibility
|
|
139
|
+
runInit(architecture, 'src', process.cwd());
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Interactive path
|
|
143
|
+
promptInitAnswers()
|
|
144
|
+
.then(({ architecture: arch, srcDir }) => {
|
|
145
|
+
runInit(arch, srcDir, process.cwd());
|
|
146
|
+
})
|
|
147
|
+
.catch((err) => {
|
|
148
|
+
// ExitPromptError is thrown when user presses Ctrl+C
|
|
149
|
+
const isCancel = err instanceof Error && err.name === 'ExitPromptError';
|
|
150
|
+
if (isCancel) {
|
|
151
|
+
console.log(chalk_1.default.yellow('\n Cancelled.\n'));
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
throw err;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/init/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic rules for classifying existing directory names
|
|
3
|
+
* into their FSD / modular equivalents.
|
|
4
|
+
*
|
|
5
|
+
* Patterns are evaluated in order — first match wins.
|
|
6
|
+
* This file owns all classification knowledge; plan-builder.ts is pure logic.
|
|
7
|
+
*/
|
|
8
|
+
export interface LayerHeuristic {
|
|
9
|
+
pattern: RegExp;
|
|
10
|
+
layer: string;
|
|
11
|
+
/** Optional sub-directory inside the layer, e.g. "ui" → shared/ui */
|
|
12
|
+
subdir?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const FSD_HEURISTICS: LayerHeuristic[];
|
|
15
|
+
/**
|
|
16
|
+
* Returns the FSD destination for a given directory name,
|
|
17
|
+
* or `null` if no heuristic matches.
|
|
18
|
+
*/
|
|
19
|
+
export declare function classifyDir(dirName: string): {
|
|
20
|
+
layer: string;
|
|
21
|
+
subdir?: string;
|
|
22
|
+
} | null;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Heuristic rules for classifying existing directory names
|
|
4
|
+
* into their FSD / modular equivalents.
|
|
5
|
+
*
|
|
6
|
+
* Patterns are evaluated in order — first match wins.
|
|
7
|
+
* This file owns all classification knowledge; plan-builder.ts is pure logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.FSD_HEURISTICS = void 0;
|
|
11
|
+
exports.classifyDir = classifyDir;
|
|
12
|
+
exports.FSD_HEURISTICS = [
|
|
13
|
+
// ── App / bootstrap ─────────────────────────────────────────────────────
|
|
14
|
+
{ pattern: /^(app|bootstrap|main|root|providers?|router|store)$/i, layer: 'app' },
|
|
15
|
+
// ── Pages / routes ──────────────────────────────────────────────────────
|
|
16
|
+
{ pattern: /^(pages?|routes?|screens?|views?)$/i, layer: 'pages' },
|
|
17
|
+
// ── Widgets (composite blocks) ──────────────────────────────────────────
|
|
18
|
+
{ pattern: /^(widgets?|layouts?|containers?|blocks?)$/i, layer: 'widgets' },
|
|
19
|
+
// ── Features (user actions) ─────────────────────────────────────────────
|
|
20
|
+
{ pattern: /^(features?|use-?cases?|actions?|mutations?)$/i, layer: 'features' },
|
|
21
|
+
// ── Entities (domain models) ────────────────────────────────────────────
|
|
22
|
+
{ pattern: /^(entities|models?|domain|types?|interfaces?)$/i, layer: 'entities' },
|
|
23
|
+
// ── Shared UI ───────────────────────────────────────────────────────────
|
|
24
|
+
{ pattern: /^(ui|components?|elements?|atoms?|molecules?)$/i, layer: 'shared', subdir: 'ui' },
|
|
25
|
+
// ── Shared utilities ────────────────────────────────────────────────────
|
|
26
|
+
{ pattern: /^(utils?|helpers?|lib|libs?|common|shared)$/i, layer: 'shared' },
|
|
27
|
+
// ── Shared API ──────────────────────────────────────────────────────────
|
|
28
|
+
{ pattern: /^(api|services?|http|clients?|fetchers?)$/i, layer: 'shared', subdir: 'api' },
|
|
29
|
+
// ── Shared hooks ────────────────────────────────────────────────────────
|
|
30
|
+
{ pattern: /^(hooks?)$/i, layer: 'shared', subdir: 'hooks' },
|
|
31
|
+
// ── Shared config ───────────────────────────────────────────────────────
|
|
32
|
+
{ pattern: /^(constants?|config|configs?|settings?)$/i, layer: 'shared', subdir: 'config' },
|
|
33
|
+
// ── Assets (shared but no rename needed) ────────────────────────────────
|
|
34
|
+
{ pattern: /^(assets?|images?|icons?|fonts?|static)$/i, layer: 'shared', subdir: 'assets' },
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Returns the FSD destination for a given directory name,
|
|
38
|
+
* or `null` if no heuristic matches.
|
|
39
|
+
*/
|
|
40
|
+
function classifyDir(dirName) {
|
|
41
|
+
for (const h of exports.FSD_HEURISTICS) {
|
|
42
|
+
if (h.pattern.test(dirName)) {
|
|
43
|
+
return { layer: h.layer, subdir: h.subdir };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../../src/commands/migrate/classifier.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAgDH,kCASC;AAhDY,QAAA,cAAc,GAAqB;IAC9C,2EAA2E;IAC3E,EAAE,OAAO,EAAE,sDAAsD,EAAE,KAAK,EAAE,KAAK,EAAE;IAEjF,2EAA2E;IAC3E,EAAE,OAAO,EAAE,qCAAqC,EAAE,KAAK,EAAE,OAAO,EAAE;IAElE,2EAA2E;IAC3E,EAAE,OAAO,EAAE,4CAA4C,EAAE,KAAK,EAAE,SAAS,EAAE;IAE3E,2EAA2E;IAC3E,EAAE,OAAO,EAAE,gDAAgD,EAAE,KAAK,EAAE,UAAU,EAAE;IAEhF,2EAA2E;IAC3E,EAAE,OAAO,EAAE,iDAAiD,EAAE,KAAK,EAAE,UAAU,EAAE;IAEjF,2EAA2E;IAC3E,EAAE,OAAO,EAAE,iDAAiD,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;IAE7F,2EAA2E;IAC3E,EAAE,OAAO,EAAE,8CAA8C,EAAE,KAAK,EAAE,QAAQ,EAAE;IAE5E,2EAA2E;IAC3E,EAAE,OAAO,EAAE,4CAA4C,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE;IAEzF,2EAA2E;IAC3E,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;IAE5D,2EAA2E;IAC3E,EAAE,OAAO,EAAE,2CAA2C,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE;IAE3F,2EAA2E;IAC3E,EAAE,OAAO,EAAE,2CAA2C,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE;CAC5F,CAAA;AAED;;;GAGG;AACH,SAAgB,WAAW,CACzB,OAAe;IAEf,KAAK,MAAM,CAAC,IAAI,sBAAc,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAA;QAC7C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Architecture } from '../../types/folder-tree';
|
|
2
|
+
export { buildMigrationPlan, scanTopLevelDirs } from './plan-builder';
|
|
3
|
+
export { classifyDir } from './classifier';
|
|
4
|
+
export type { FileMoveProposal, MigrationPlan } from './plan-builder';
|
|
5
|
+
/**
|
|
6
|
+
* CLI entry point for the migrate command.
|
|
7
|
+
*/
|
|
8
|
+
export declare function migrateCommand(targetArchitecture: Architecture): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.classifyDir = exports.scanTopLevelDirs = exports.buildMigrationPlan = void 0;
|
|
7
|
+
exports.migrateCommand = migrateCommand;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const config_1 = require("../../utils/config");
|
|
11
|
+
const logger_1 = require("../../utils/logger");
|
|
12
|
+
const plan_builder_1 = require("./plan-builder");
|
|
13
|
+
const printer_1 = require("./printer");
|
|
14
|
+
var plan_builder_2 = require("./plan-builder");
|
|
15
|
+
Object.defineProperty(exports, "buildMigrationPlan", { enumerable: true, get: function () { return plan_builder_2.buildMigrationPlan; } });
|
|
16
|
+
Object.defineProperty(exports, "scanTopLevelDirs", { enumerable: true, get: function () { return plan_builder_2.scanTopLevelDirs; } });
|
|
17
|
+
var classifier_1 = require("./classifier");
|
|
18
|
+
Object.defineProperty(exports, "classifyDir", { enumerable: true, get: function () { return classifier_1.classifyDir; } });
|
|
19
|
+
/**
|
|
20
|
+
* CLI entry point for the migrate command.
|
|
21
|
+
*/
|
|
22
|
+
function migrateCommand(targetArchitecture) {
|
|
23
|
+
const config = (0, config_1.loadProjectConfig)();
|
|
24
|
+
if (config.architecture === targetArchitecture) {
|
|
25
|
+
logger_1.logger.info(`Project is already configured as ${chalk_1.default.bold(targetArchitecture.toUpperCase())}.`);
|
|
26
|
+
logger_1.logger.info(`Run ${chalk_1.default.cyan('component-forge validate')} to check the current structure.`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const srcPath = node_path_1.default.join(process.cwd(), config.srcDir);
|
|
30
|
+
const plan = (0, plan_builder_1.buildMigrationPlan)(srcPath, targetArchitecture);
|
|
31
|
+
(0, printer_1.printMigrationPlan)(plan);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/migrate/index.ts"],"names":[],"mappings":";;;;;;AAkBA,wCAgBC;AAlCD,0DAA4B;AAE5B,kDAAyB;AAGzB,+CAAsD;AACtD,+CAA2C;AAE3C,iDAAmD;AACnD,uCAA8C;AAE9C,+CAAqE;AAA5D,kHAAA,kBAAkB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAC7C,2CAA0C;AAAjC,yGAAA,WAAW,OAAA;AAGpB;;GAEG;AACH,SAAgB,cAAc,CAAC,kBAAgC;IAC7D,MAAM,MAAM,GAAG,IAAA,0BAAiB,GAAE,CAAA;IAElC,IAAI,MAAM,CAAC,YAAY,KAAK,kBAAkB,EAAE,CAAC;QAC/C,eAAM,CAAC,IAAI,CACT,oCAAoC,eAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC,GAAG,CACpF,CAAA;QACD,eAAM,CAAC,IAAI,CACT,OAAO,eAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,kCAAkC,CAChF,CAAA;QACD,OAAM;IACR,CAAC;IAED,MAAM,OAAO,GAAG,mBAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,IAAA,iCAAkB,EAAC,OAAO,EAAE,kBAAkB,CAAC,CAAA;IAC5D,IAAA,4BAAkB,EAAC,IAAI,CAAC,CAAA;AAC1B,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Architecture } from '../../types/folder-tree';
|
|
2
|
+
export interface FileMoveProposal {
|
|
3
|
+
/** Current directory name relative to srcDir */
|
|
4
|
+
from: string;
|
|
5
|
+
/** Proposed path relative to srcDir */
|
|
6
|
+
to: string;
|
|
7
|
+
/** Human-readable reason for the move */
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MigrationPlan {
|
|
11
|
+
targetArchitecture: Architecture;
|
|
12
|
+
sourceDir: string;
|
|
13
|
+
proposals: FileMoveProposal[];
|
|
14
|
+
/** Directories that didn't match any known pattern — need manual review */
|
|
15
|
+
unknownFiles: string[];
|
|
16
|
+
summary: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Returns the names of top-level directories inside srcPath.
|
|
20
|
+
* Files at the root level are intentionally ignored.
|
|
21
|
+
*/
|
|
22
|
+
export declare function scanTopLevelDirs(srcPath: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Builds a migration plan by analysing top-level directories in srcPath.
|
|
25
|
+
* Pure function — no filesystem writes, safe to call at any time.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildMigrationPlan(srcPath: string, targetArch: Architecture): MigrationPlan;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scanTopLevelDirs = scanTopLevelDirs;
|
|
7
|
+
exports.buildMigrationPlan = buildMigrationPlan;
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const classifier_1 = require("./classifier");
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Directory scanner
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Returns the names of top-level directories inside srcPath.
|
|
16
|
+
* Files at the root level are intentionally ignored.
|
|
17
|
+
*/
|
|
18
|
+
function scanTopLevelDirs(srcPath) {
|
|
19
|
+
if (!fs_extra_1.default.existsSync(srcPath))
|
|
20
|
+
return [];
|
|
21
|
+
return fs_extra_1.default
|
|
22
|
+
.readdirSync(srcPath, { withFileTypes: true })
|
|
23
|
+
.filter((e) => e.isDirectory())
|
|
24
|
+
.map((e) => e.name);
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Plan builder
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
const FSD_LAYERS = new Set([
|
|
30
|
+
'app', 'processes', 'pages', 'widgets', 'features', 'entities', 'shared',
|
|
31
|
+
]);
|
|
32
|
+
const MODULAR_BASE = new Set(['modules', 'shared', 'core']);
|
|
33
|
+
function buildFsdProposals(dirs) {
|
|
34
|
+
const proposals = [];
|
|
35
|
+
const unknownFiles = [];
|
|
36
|
+
for (const dir of dirs) {
|
|
37
|
+
if (FSD_LAYERS.has(dir))
|
|
38
|
+
continue; // already correct
|
|
39
|
+
const classification = (0, classifier_1.classifyDir)(dir);
|
|
40
|
+
if (!classification) {
|
|
41
|
+
unknownFiles.push(dir);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const { layer, subdir } = classification;
|
|
45
|
+
const to = subdir ? node_path_1.default.join(layer, subdir, dir) : node_path_1.default.join(layer, dir);
|
|
46
|
+
proposals.push({
|
|
47
|
+
from: dir,
|
|
48
|
+
to,
|
|
49
|
+
reason: `"${dir}" maps to FSD layer "${layer}"${subdir ? `/${subdir}` : ''}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return { proposals, unknownFiles };
|
|
53
|
+
}
|
|
54
|
+
function buildModularProposals(dirs) {
|
|
55
|
+
const proposals = [];
|
|
56
|
+
for (const dir of dirs) {
|
|
57
|
+
if (MODULAR_BASE.has(dir))
|
|
58
|
+
continue;
|
|
59
|
+
const classification = (0, classifier_1.classifyDir)(dir);
|
|
60
|
+
const isSharedCandidate = classification?.layer === 'shared' &&
|
|
61
|
+
!['api', 'services'].includes(dir.toLowerCase());
|
|
62
|
+
if (isSharedCandidate) {
|
|
63
|
+
proposals.push({
|
|
64
|
+
from: dir,
|
|
65
|
+
to: node_path_1.default.join('shared', dir),
|
|
66
|
+
reason: `"${dir}" is a shared utility/UI — belongs under shared/`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
proposals.push({
|
|
71
|
+
from: dir,
|
|
72
|
+
to: node_path_1.default.join('modules', dir),
|
|
73
|
+
reason: `"${dir}" is a feature domain — wrap as a module`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return proposals;
|
|
78
|
+
}
|
|
79
|
+
function buildSummary(proposals, unknownFiles, target) {
|
|
80
|
+
if (proposals.length === 0 && unknownFiles.length === 0) {
|
|
81
|
+
return `No changes needed — project already looks like ${target.toUpperCase()}.`;
|
|
82
|
+
}
|
|
83
|
+
const moved = proposals.length;
|
|
84
|
+
const unknown = unknownFiles.length;
|
|
85
|
+
return (`${moved} director${moved === 1 ? 'y' : 'ies'} to move` +
|
|
86
|
+
(unknown > 0 ? `, ${unknown} unknown (manual review needed)` : '') +
|
|
87
|
+
'.');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Builds a migration plan by analysing top-level directories in srcPath.
|
|
91
|
+
* Pure function — no filesystem writes, safe to call at any time.
|
|
92
|
+
*/
|
|
93
|
+
function buildMigrationPlan(srcPath, targetArch) {
|
|
94
|
+
const dirs = scanTopLevelDirs(srcPath);
|
|
95
|
+
const { proposals, unknownFiles } = targetArch === 'fsd'
|
|
96
|
+
? buildFsdProposals(dirs)
|
|
97
|
+
: { proposals: buildModularProposals(dirs), unknownFiles: [] };
|
|
98
|
+
return {
|
|
99
|
+
targetArchitecture: targetArch,
|
|
100
|
+
sourceDir: srcPath,
|
|
101
|
+
proposals,
|
|
102
|
+
unknownFiles,
|
|
103
|
+
summary: buildSummary(proposals, unknownFiles, targetArch),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=plan-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-builder.js","sourceRoot":"","sources":["../../../src/commands/migrate/plan-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printMigrationPlan = printMigrationPlan;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
/**
|
|
10
|
+
* Renders a MigrationPlan as colour-coded CLI output.
|
|
11
|
+
* Pure function — receives the plan, writes to stdout.
|
|
12
|
+
*/
|
|
13
|
+
function printMigrationPlan(plan) {
|
|
14
|
+
const { proposals, unknownFiles, summary, targetArchitecture, sourceDir } = plan;
|
|
15
|
+
const rel = node_path_1.default.relative(process.cwd(), sourceDir) || '.';
|
|
16
|
+
console.log(chalk_1.default.bold(`\n Migration plan → ${chalk_1.default.cyan(targetArchitecture.toUpperCase())}\n`));
|
|
17
|
+
console.log(chalk_1.default.gray(` Analysed: ${rel}\n`));
|
|
18
|
+
console.log(chalk_1.default.gray(` ${'─'.repeat(58)}`));
|
|
19
|
+
if (proposals.length === 0 && unknownFiles.length === 0) {
|
|
20
|
+
console.log(chalk_1.default.green(`\n ✓ ${summary}\n`));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (proposals.length > 0) {
|
|
24
|
+
console.log(chalk_1.default.bold(`\n Proposed moves (${proposals.length}):\n`));
|
|
25
|
+
for (const p of proposals) {
|
|
26
|
+
const fromFmt = chalk_1.default.yellow(p.from.padEnd(24));
|
|
27
|
+
const toFmt = chalk_1.default.green(p.to);
|
|
28
|
+
console.log(` ${fromFmt} → ${toFmt}`);
|
|
29
|
+
console.log(chalk_1.default.gray(` ↳ ${p.reason}`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (unknownFiles.length > 0) {
|
|
33
|
+
console.log(chalk_1.default.bold(`\n Needs manual review (${unknownFiles.length}):\n`));
|
|
34
|
+
for (const f of unknownFiles) {
|
|
35
|
+
console.log(` ${chalk_1.default.red('?')} ${chalk_1.default.white(f)}`);
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk_1.default.gray("\n These directories don't match known patterns."));
|
|
38
|
+
console.log(chalk_1.default.gray(` Run ${chalk_1.default.white('component-forge explain fsd')} to understand the target structure.`));
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk_1.default.gray(`\n ${'─'.repeat(58)}`));
|
|
41
|
+
console.log(chalk_1.default.bold(`\n Summary: ${summary}\n`));
|
|
42
|
+
console.log(chalk_1.default.yellow(' ⚠ This is a dry-run analysis — no files were moved.\n'));
|
|
43
|
+
console.log(chalk_1.default.gray(" To apply: move each directory manually or use your IDE's refactor tools.\n"));
|
|
44
|
+
console.log(chalk_1.default.gray(` After moving, run ${chalk_1.default.white('component-forge validate')} and ${chalk_1.default.white('component-forge check')} to verify.\n`));
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=printer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"printer.js","sourceRoot":"","sources":["../../../src/commands/migrate/printer.ts"],"names":[],"mappings":";;;;;AAUA,gDAsDC;AAhED,0DAA4B;AAE5B,kDAAyB;AAIzB;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,IAAmB;IACpD,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,GAAG,IAAI,CAAA;IAChF,MAAM,GAAG,GAAG,mBAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,IAAI,GAAG,CAAA;IAE1D,OAAO,CAAC,GAAG,CACT,eAAK,CAAC,IAAI,CAAC,wBAAwB,eAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CACrF,CAAA;IACD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAA;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAE9C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,SAAS,OAAO,IAAI,CAAC,CAAC,CAAA;QAC9C,OAAM;IACR,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,uBAAuB,SAAS,CAAC,MAAM,MAAM,CAAC,CAAC,CAAA;QAEtE,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,eAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;YAC/C,MAAM,KAAK,GAAG,eAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YAC/B,OAAO,CAAC,GAAG,CAAC,OAAO,OAAO,MAAM,KAAK,EAAE,CAAC,CAAA;YACxC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAChD,CAAC;IACH,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,4BAA4B,YAAY,CAAC,MAAM,MAAM,CAAC,CAAC,CAAA;QAE9E,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,OAAO,eAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,eAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QACxD,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAA;QAC9E,OAAO,CAAC,GAAG,CACT,eAAK,CAAC,IAAI,CACR,WAAW,eAAK,CAAC,KAAK,CAAC,6BAA6B,CAAC,sCAAsC,CAC5F,CACF,CAAA;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IAChD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,gBAAgB,OAAO,IAAI,CAAC,CAAC,CAAA;IACpD,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,yDAAyD,CAAC,CAAC,CAAA;IACpF,OAAO,CAAC,GAAG,CACT,eAAK,CAAC,IAAI,CACR,8EAA8E,CAC/E,CACF,CAAA;IACD,OAAO,CAAC,GAAG,CACT,eAAK,CAAC,IAAI,CACR,uBAAuB,eAAK,CAAC,KAAK,CAAC,0BAA0B,CAAC,QAAQ,eAAK,CAAC,KAAK,CAAC,uBAAuB,CAAC,eAAe,CAC1H,CACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Architecture } from '../../types/folder-tree';
|
|
2
|
+
interface LayerRule {
|
|
3
|
+
/** Directories that MUST exist */
|
|
4
|
+
required: string[];
|
|
5
|
+
/** All allowed directories (required + optional) */
|
|
6
|
+
allowed: string[];
|
|
7
|
+
}
|
|
8
|
+
type Severity = 'error' | 'warning';
|
|
9
|
+
export interface ValidationIssue {
|
|
10
|
+
severity: Severity;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Checks that all required layers exist under srcDir
|
|
15
|
+
*/
|
|
16
|
+
export declare function checkRequiredLayers(srcPath: string, rule: LayerRule, srcDir: string): ValidationIssue[];
|
|
17
|
+
/**
|
|
18
|
+
* Checks that no unknown (forbidden) directories exist directly under srcDir
|
|
19
|
+
*/
|
|
20
|
+
export declare function checkUnknownLayers(srcPath: string, rule: LayerRule, architecture: Architecture, srcDir: string): ValidationIssue[];
|
|
21
|
+
/**
|
|
22
|
+
* Checks each slice for a public API index.ts file
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkPublicApiFiles(srcPath: string, rule: LayerRule, srcDir: string): ValidationIssue[];
|
|
25
|
+
export declare function validateCommand(): void;
|
|
26
|
+
export {};
|
|
@@ -3,11 +3,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkRequiredLayers = checkRequiredLayers;
|
|
7
|
+
exports.checkUnknownLayers = checkUnknownLayers;
|
|
8
|
+
exports.checkPublicApiFiles = checkPublicApiFiles;
|
|
6
9
|
exports.validateCommand = validateCommand;
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const config_1 = require("../../utils/config");
|
|
13
|
+
const logger_1 = require("../../utils/logger");
|
|
11
14
|
const layerRulesByArchitecture = {
|
|
12
15
|
fsd: {
|
|
13
16
|
required: ['app', 'shared'],
|
|
@@ -19,7 +22,7 @@ const layerRulesByArchitecture = {
|
|
|
19
22
|
},
|
|
20
23
|
};
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
22
|
-
// Validation rules
|
|
25
|
+
// Validation rules — exported for unit testing
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
24
27
|
/**
|
|
25
28
|
* Checks that all required layers exist under srcDir
|
|
@@ -116,4 +119,4 @@ function validateCommand() {
|
|
|
116
119
|
process.exit(1);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
|
-
//# sourceMappingURL=
|
|
122
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/validate/index.ts"],"names":[],"mappings":"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"}
|