@openwebf/webf 0.23.10 → 0.24.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/README.md +8 -0
- package/bin/webf.js +9 -1
- package/dist/agents.js +245 -0
- package/dist/commands.js +101 -7
- package/dist/generator.js +37 -36
- package/dist/module.js +43 -14
- package/dist/peerDeps.js +27 -0
- package/dist/react.js +10 -18
- package/dist/vue.js +138 -132
- package/package.json +2 -1
- package/src/agents.ts +267 -0
- package/src/commands.ts +110 -8
- package/src/generator.ts +38 -37
- package/src/module.ts +53 -21
- package/src/peerDeps.ts +21 -0
- package/src/react.ts +10 -18
- package/src/vue.ts +157 -142
- package/templates/react.component.tsx.tpl +2 -2
- package/templates/react.index.ts.tpl +2 -1
- package/templates/react.package.json.tpl +3 -4
- package/templates/react.tsconfig.json.tpl +8 -1
- package/templates/react.tsup.config.ts.tpl +1 -1
- package/templates/vue.component.partial.tpl +4 -4
- package/templates/vue.components.d.ts.tpl +24 -9
- package/templates/vue.package.json.tpl +3 -1
- package/test/agents-init.test.ts +80 -0
- package/test/commands.test.ts +5 -12
- package/test/generator.test.ts +17 -10
- package/test/peerDeps.test.ts +30 -0
- package/test/react-consts.test.ts +9 -3
- package/test/standard-props.test.ts +14 -14
- package/test/templates.test.ts +17 -0
- package/test/vue.test.ts +36 -11
- package/dist/constants.js +0 -242
package/README.md
CHANGED
|
@@ -10,6 +10,14 @@ npm install -g @openwebf/webf
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
+
### Initialize Claude Code Skills
|
|
14
|
+
|
|
15
|
+
The `webf agents init` command injects WebF Claude Code skills (from `@openwebf/claude-code-skills`) into your project and updates `CLAUDE.md` to reference them.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
webf agents init [project-dir]
|
|
19
|
+
```
|
|
20
|
+
|
|
13
21
|
### Generate Code
|
|
14
22
|
|
|
15
23
|
The `webf codegen` command generates Dart abstract classes and React/Vue components from TypeScript definitions. It automatically creates a project if needed.
|
package/bin/webf.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
4
|
const version = require('../package.json').version;
|
|
5
|
-
const { generateCommand, generateModuleCommand } = require('../dist/commands');
|
|
5
|
+
const { generateCommand, generateModuleCommand, agentsInitCommand } = require('../dist/commands');
|
|
6
6
|
|
|
7
7
|
const program = new Command();
|
|
8
8
|
|
|
@@ -35,4 +35,12 @@ program
|
|
|
35
35
|
.description('Generate NPM package and Dart bindings for a WebF module from TypeScript interfaces (*.module.d.ts)')
|
|
36
36
|
.action(generateModuleCommand);
|
|
37
37
|
|
|
38
|
+
const agents = program.command('agents').description('Manage Claude Code agent configs');
|
|
39
|
+
|
|
40
|
+
agents
|
|
41
|
+
.command('init')
|
|
42
|
+
.argument('[projectDir]', 'Target project directory', '.')
|
|
43
|
+
.description('Initialize Claude Code skills config for a project')
|
|
44
|
+
.action(agentsInitCommand);
|
|
45
|
+
|
|
38
46
|
program.parse();
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.agentsInitCommand = agentsInitCommand;
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const os_1 = __importDefault(require("os"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
20
|
+
const WEBF_AGENTS_BLOCK_START = '<!-- webf-agents:init start -->';
|
|
21
|
+
const WEBF_AGENTS_BLOCK_END = '<!-- webf-agents:init end -->';
|
|
22
|
+
function ensureDirSync(dirPath) {
|
|
23
|
+
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
function readFileIfExists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return fs_1.default.readFileSync(filePath);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if ((error === null || error === void 0 ? void 0 : error.code) === 'ENOENT')
|
|
31
|
+
return null;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function backupFileSync(filePath) {
|
|
36
|
+
const timestamp = new Date()
|
|
37
|
+
.toISOString()
|
|
38
|
+
.replace(/[:.]/g, '')
|
|
39
|
+
.replace('T', '')
|
|
40
|
+
.replace('Z', 'Z');
|
|
41
|
+
const backupPath = `${filePath}.bak.${timestamp}`;
|
|
42
|
+
fs_1.default.copyFileSync(filePath, backupPath);
|
|
43
|
+
return backupPath;
|
|
44
|
+
}
|
|
45
|
+
function copyFileWithBackupSync(srcPath, destPath) {
|
|
46
|
+
const src = fs_1.default.readFileSync(srcPath);
|
|
47
|
+
const dest = readFileIfExists(destPath);
|
|
48
|
+
if (dest && Buffer.compare(src, dest) === 0)
|
|
49
|
+
return { changed: false, backupPath: null };
|
|
50
|
+
ensureDirSync(path_1.default.dirname(destPath));
|
|
51
|
+
let backupPath = null;
|
|
52
|
+
if (dest) {
|
|
53
|
+
backupPath = backupFileSync(destPath);
|
|
54
|
+
}
|
|
55
|
+
fs_1.default.writeFileSync(destPath, src);
|
|
56
|
+
return { changed: true, backupPath };
|
|
57
|
+
}
|
|
58
|
+
function copyDirRecursiveSync(srcDir, destDir, stats) {
|
|
59
|
+
ensureDirSync(destDir);
|
|
60
|
+
const entries = fs_1.default.readdirSync(srcDir, { withFileTypes: true });
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const srcPath = path_1.default.join(srcDir, entry.name);
|
|
63
|
+
const destPath = path_1.default.join(destDir, entry.name);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
copyDirRecursiveSync(srcPath, destPath, stats);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (entry.isFile()) {
|
|
69
|
+
const { changed, backupPath } = copyFileWithBackupSync(srcPath, destPath);
|
|
70
|
+
if (changed)
|
|
71
|
+
stats.filesWritten += 1;
|
|
72
|
+
else
|
|
73
|
+
stats.filesUnchanged += 1;
|
|
74
|
+
if (backupPath)
|
|
75
|
+
stats.backupsCreated += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function listFilesRecursiveSync(dirPath) {
|
|
80
|
+
const out = [];
|
|
81
|
+
const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true });
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
const entryPath = path_1.default.join(dirPath, entry.name);
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
out.push(...listFilesRecursiveSync(entryPath));
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (entry.isFile())
|
|
89
|
+
out.push(entryPath);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function toPosixRelativePath(fromDir, absolutePath) {
|
|
94
|
+
const rel = path_1.default.relative(fromDir, absolutePath);
|
|
95
|
+
return rel.split(path_1.default.sep).join('/');
|
|
96
|
+
}
|
|
97
|
+
function parseSkillFrontmatter(skillMd) {
|
|
98
|
+
var _a;
|
|
99
|
+
const trimmed = skillMd.trimStart();
|
|
100
|
+
if (!trimmed.startsWith('---'))
|
|
101
|
+
return {};
|
|
102
|
+
const endIndex = trimmed.indexOf('\n---', 3);
|
|
103
|
+
if (endIndex === -1)
|
|
104
|
+
return {};
|
|
105
|
+
const fm = trimmed.slice(3, endIndex).trim();
|
|
106
|
+
try {
|
|
107
|
+
const parsed = (_a = yaml_1.default.parse(fm)) !== null && _a !== void 0 ? _a : {};
|
|
108
|
+
return { name: parsed === null || parsed === void 0 ? void 0 : parsed.name, description: parsed === null || parsed === void 0 ? void 0 : parsed.description };
|
|
109
|
+
}
|
|
110
|
+
catch (_b) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function updateOrAppendMarkedBlock(existing, newBlock) {
|
|
115
|
+
const startIndex = existing.indexOf(WEBF_AGENTS_BLOCK_START);
|
|
116
|
+
const endIndex = existing.indexOf(WEBF_AGENTS_BLOCK_END);
|
|
117
|
+
if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
|
|
118
|
+
const before = existing.slice(0, startIndex).trimEnd();
|
|
119
|
+
const after = existing.slice(endIndex + WEBF_AGENTS_BLOCK_END.length).trimStart();
|
|
120
|
+
const next = [before, newBlock.trim(), after].filter(Boolean).join('\n\n');
|
|
121
|
+
return { content: next + '\n', action: 'replaced' };
|
|
122
|
+
}
|
|
123
|
+
const content = existing.trimEnd();
|
|
124
|
+
return { content: (content ? content + '\n\n' : '') + newBlock.trim() + '\n', action: 'appended' };
|
|
125
|
+
}
|
|
126
|
+
function buildClaudeBlock(sourcePackageName, sourcePackageVersion, skills) {
|
|
127
|
+
const lines = [];
|
|
128
|
+
lines.push(WEBF_AGENTS_BLOCK_START);
|
|
129
|
+
lines.push('## WebF Claude Code Skills');
|
|
130
|
+
lines.push('');
|
|
131
|
+
lines.push(`Source: \`${sourcePackageName}@${sourcePackageVersion}\``);
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push('### Skills');
|
|
134
|
+
for (const skill of skills) {
|
|
135
|
+
const desc = skill.description ? ` — ${skill.description}` : '';
|
|
136
|
+
lines.push(`- \`${skill.name}\`${desc} (\`${skill.skillFileRelativePath}\`)`);
|
|
137
|
+
}
|
|
138
|
+
const anyReferences = skills.some(s => s.referenceRelativePaths.length > 0);
|
|
139
|
+
if (anyReferences) {
|
|
140
|
+
lines.push('');
|
|
141
|
+
lines.push('### References');
|
|
142
|
+
for (const skill of skills) {
|
|
143
|
+
if (skill.referenceRelativePaths.length === 0)
|
|
144
|
+
continue;
|
|
145
|
+
const refs = skill.referenceRelativePaths.map(r => `\`${r}\``).join(', ');
|
|
146
|
+
lines.push(`- \`${skill.name}\`: ${refs}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
lines.push(WEBF_AGENTS_BLOCK_END);
|
|
150
|
+
return lines.join('\n');
|
|
151
|
+
}
|
|
152
|
+
function resolveSkillsPackageRoot() {
|
|
153
|
+
var _a, _b;
|
|
154
|
+
const packageJsonPath = require.resolve('@openwebf/claude-code-skills/package.json');
|
|
155
|
+
const packageRoot = path_1.default.dirname(packageJsonPath);
|
|
156
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
157
|
+
return {
|
|
158
|
+
packageRoot,
|
|
159
|
+
packageName: (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.name) !== null && _a !== void 0 ? _a : '@openwebf/claude-code-skills',
|
|
160
|
+
packageVersion: (_b = packageJson === null || packageJson === void 0 ? void 0 : packageJson.version) !== null && _b !== void 0 ? _b : 'unknown',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function listSkillDirectories(skillsPackageRoot) {
|
|
164
|
+
const entries = fs_1.default.readdirSync(skillsPackageRoot, { withFileTypes: true });
|
|
165
|
+
const dirs = entries
|
|
166
|
+
.filter(e => e.isDirectory())
|
|
167
|
+
.map(e => e.name)
|
|
168
|
+
.filter(name => fs_1.default.existsSync(path_1.default.join(skillsPackageRoot, name, 'SKILL.md')))
|
|
169
|
+
.sort();
|
|
170
|
+
return dirs;
|
|
171
|
+
}
|
|
172
|
+
function collectSkillInfo(projectRoot, skillsDir, skillDirectoryName) {
|
|
173
|
+
var _a;
|
|
174
|
+
const skillDirAbs = path_1.default.join(skillsDir, skillDirectoryName);
|
|
175
|
+
const skillMdAbs = path_1.default.join(skillDirAbs, 'SKILL.md');
|
|
176
|
+
const skillMd = fs_1.default.readFileSync(skillMdAbs, 'utf-8');
|
|
177
|
+
const fm = parseSkillFrontmatter(skillMd);
|
|
178
|
+
const referenceRelativePaths = [];
|
|
179
|
+
const files = listFilesRecursiveSync(skillDirAbs);
|
|
180
|
+
for (const fileAbs of files) {
|
|
181
|
+
if (path_1.default.basename(fileAbs) === 'SKILL.md')
|
|
182
|
+
continue;
|
|
183
|
+
referenceRelativePaths.push(toPosixRelativePath(projectRoot, fileAbs));
|
|
184
|
+
}
|
|
185
|
+
referenceRelativePaths.sort();
|
|
186
|
+
return {
|
|
187
|
+
directoryName: skillDirectoryName,
|
|
188
|
+
name: (_a = fm.name) !== null && _a !== void 0 ? _a : skillDirectoryName,
|
|
189
|
+
description: fm.description,
|
|
190
|
+
skillFileRelativePath: toPosixRelativePath(projectRoot, skillMdAbs),
|
|
191
|
+
referenceRelativePaths,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function agentsInitCommand(projectDir) {
|
|
195
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
196
|
+
var _a, _b;
|
|
197
|
+
const startedAt = Date.now();
|
|
198
|
+
const resolvedProjectDir = path_1.default.resolve(process.cwd(), projectDir || '.');
|
|
199
|
+
const claudeMdPath = path_1.default.join(resolvedProjectDir, 'CLAUDE.md');
|
|
200
|
+
const claudeDir = path_1.default.join(resolvedProjectDir, '.claude');
|
|
201
|
+
const projectSkillsDir = path_1.default.join(claudeDir, 'skills');
|
|
202
|
+
const hasClaudeMd = fs_1.default.existsSync(claudeMdPath);
|
|
203
|
+
const hasClaudeDir = fs_1.default.existsSync(claudeDir);
|
|
204
|
+
const isNewProject = !hasClaudeMd && !hasClaudeDir;
|
|
205
|
+
console.log('webf agents init');
|
|
206
|
+
console.log(`Project: ${resolvedProjectDir}`);
|
|
207
|
+
if (isNewProject) {
|
|
208
|
+
console.log('Detected: no CLAUDE.md and no .claude/ (new project)');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.log(`Detected: CLAUDE.md=${hasClaudeMd ? 'yes' : 'no'}, .claude/=${hasClaudeDir ? 'yes' : 'no'} (existing project)`);
|
|
212
|
+
}
|
|
213
|
+
const { packageRoot, packageName, packageVersion } = resolveSkillsPackageRoot();
|
|
214
|
+
const skillDirectories = listSkillDirectories(packageRoot);
|
|
215
|
+
if (skillDirectories.length === 0) {
|
|
216
|
+
throw new Error(`No skills found in ${packageName} (resolved at ${packageRoot}).`);
|
|
217
|
+
}
|
|
218
|
+
console.log(`Skills source: ${packageName}@${packageVersion}`);
|
|
219
|
+
console.log(`Skills destination: ${toPosixRelativePath(resolvedProjectDir, projectSkillsDir)}`);
|
|
220
|
+
ensureDirSync(projectSkillsDir);
|
|
221
|
+
const copyStats = { filesWritten: 0, filesUnchanged: 0, backupsCreated: 0 };
|
|
222
|
+
for (const skillDirName of skillDirectories) {
|
|
223
|
+
const srcSkillDir = path_1.default.join(packageRoot, skillDirName);
|
|
224
|
+
const destSkillDir = path_1.default.join(projectSkillsDir, skillDirName);
|
|
225
|
+
copyDirRecursiveSync(srcSkillDir, destSkillDir, copyStats);
|
|
226
|
+
}
|
|
227
|
+
const installedSkills = skillDirectories.map(skillDirName => collectSkillInfo(resolvedProjectDir, projectSkillsDir, skillDirName));
|
|
228
|
+
const block = buildClaudeBlock(packageName, packageVersion, installedSkills);
|
|
229
|
+
if (isNewProject) {
|
|
230
|
+
const initial = ['# Claude Code', '', block, ''].join('\n');
|
|
231
|
+
fs_1.default.writeFileSync(claudeMdPath, initial, 'utf-8');
|
|
232
|
+
console.log(`Created ${toPosixRelativePath(resolvedProjectDir, claudeMdPath)}`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
const existing = (_b = (_a = readFileIfExists(claudeMdPath)) === null || _a === void 0 ? void 0 : _a.toString('utf-8')) !== null && _b !== void 0 ? _b : '';
|
|
236
|
+
const { content, action } = updateOrAppendMarkedBlock(existing, block);
|
|
237
|
+
fs_1.default.writeFileSync(claudeMdPath, content, 'utf-8');
|
|
238
|
+
console.log(`${action === 'replaced' ? 'Updated' : 'Appended'} WebF skills block in ${toPosixRelativePath(resolvedProjectDir, claudeMdPath)}`);
|
|
239
|
+
}
|
|
240
|
+
const versionFilePath = path_1.default.join(claudeDir, 'webf-claude-code-skills.version');
|
|
241
|
+
fs_1.default.writeFileSync(versionFilePath, `${packageName}@${packageVersion}${os_1.default.EOL}`, 'utf-8');
|
|
242
|
+
console.log(`Wrote ${toPosixRelativePath(resolvedProjectDir, versionFilePath)}`);
|
|
243
|
+
console.log(`Installed ${installedSkills.length} skills (${copyStats.filesWritten} files written, ${copyStats.filesUnchanged} unchanged, ${copyStats.backupsCreated} backups) in ${Date.now() - startedAt}ms`);
|
|
244
|
+
});
|
|
245
|
+
}
|
package/dist/commands.js
CHANGED
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.agentsInitCommand = void 0;
|
|
15
16
|
exports.generateCommand = generateCommand;
|
|
16
17
|
exports.generateModuleCommand = generateModuleCommand;
|
|
17
18
|
const child_process_1 = require("child_process");
|
|
@@ -20,10 +21,13 @@ const path_1 = __importDefault(require("path"));
|
|
|
20
21
|
const os_1 = __importDefault(require("os"));
|
|
21
22
|
const generator_1 = require("./generator");
|
|
22
23
|
const module_1 = require("./module");
|
|
24
|
+
const peerDeps_1 = require("./peerDeps");
|
|
23
25
|
const glob_1 = require("glob");
|
|
24
26
|
const lodash_1 = __importDefault(require("lodash"));
|
|
25
27
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
26
28
|
const yaml_1 = __importDefault(require("yaml"));
|
|
29
|
+
const agents_1 = require("./agents");
|
|
30
|
+
Object.defineProperty(exports, "agentsInitCommand", { enumerable: true, get: function () { return agents_1.agentsInitCommand; } });
|
|
27
31
|
/**
|
|
28
32
|
* Sanitize a package name to comply with npm naming rules
|
|
29
33
|
* NPM package name rules:
|
|
@@ -350,7 +354,8 @@ function createCommand(target, options) {
|
|
|
350
354
|
// Do not overwrite existing index.ts created by the user
|
|
351
355
|
// Leave merge to the codegen step which appends exports safely
|
|
352
356
|
}
|
|
353
|
-
|
|
357
|
+
// Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
|
|
358
|
+
(0, child_process_1.spawnSync)(NPM, ['install', '--production=false'], {
|
|
354
359
|
cwd: target,
|
|
355
360
|
stdio: 'inherit'
|
|
356
361
|
});
|
|
@@ -369,11 +374,8 @@ function createCommand(target, options) {
|
|
|
369
374
|
const gitignorePath = path_1.default.join(target, '.gitignore');
|
|
370
375
|
const gitignoreContent = lodash_1.default.template(gitignore)({});
|
|
371
376
|
writeFileIfChanged(gitignorePath, gitignoreContent);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
stdio: 'inherit'
|
|
375
|
-
});
|
|
376
|
-
(0, child_process_1.spawnSync)(NPM, ['install', 'vue', '-D'], {
|
|
377
|
+
// Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
|
|
378
|
+
(0, child_process_1.spawnSync)(NPM, ['install', '--production=false'], {
|
|
377
379
|
cwd: target,
|
|
378
380
|
stdio: 'inherit'
|
|
379
381
|
});
|
|
@@ -1003,7 +1005,7 @@ function ensureInitialized(targetPath) {
|
|
|
1003
1005
|
}
|
|
1004
1006
|
function buildPackage(packagePath) {
|
|
1005
1007
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1006
|
-
var _a;
|
|
1008
|
+
var _a, _b, _c;
|
|
1007
1009
|
const packageJsonPath = path_1.default.join(packagePath, 'package.json');
|
|
1008
1010
|
if (!fs_1.default.existsSync(packageJsonPath)) {
|
|
1009
1011
|
// Skip the error in test environment to avoid console warnings
|
|
@@ -1015,6 +1017,91 @@ function buildPackage(packagePath) {
|
|
|
1015
1017
|
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
1016
1018
|
const packageName = packageJson.name;
|
|
1017
1019
|
const packageVersion = packageJson.version;
|
|
1020
|
+
function getInstalledPackageJsonPath(pkgName) {
|
|
1021
|
+
const parts = pkgName.split('/');
|
|
1022
|
+
return path_1.default.join(packagePath, 'node_modules', ...parts, 'package.json');
|
|
1023
|
+
}
|
|
1024
|
+
function getInstalledPackageDir(pkgName) {
|
|
1025
|
+
const parts = pkgName.split('/');
|
|
1026
|
+
return path_1.default.join(packagePath, 'node_modules', ...parts);
|
|
1027
|
+
}
|
|
1028
|
+
function findUp(startDir, relativePathToFind) {
|
|
1029
|
+
let dir = path_1.default.resolve(startDir);
|
|
1030
|
+
while (true) {
|
|
1031
|
+
const candidate = path_1.default.join(dir, relativePathToFind);
|
|
1032
|
+
if (fs_1.default.existsSync(candidate))
|
|
1033
|
+
return candidate;
|
|
1034
|
+
const parent = path_1.default.dirname(dir);
|
|
1035
|
+
if (parent === dir)
|
|
1036
|
+
return null;
|
|
1037
|
+
dir = parent;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
function ensurePeerDependencyAvailableForBuild(peerName) {
|
|
1041
|
+
var _a, _b;
|
|
1042
|
+
const installedPkgJson = getInstalledPackageJsonPath(peerName);
|
|
1043
|
+
if (fs_1.default.existsSync(installedPkgJson))
|
|
1044
|
+
return;
|
|
1045
|
+
const peerRange = (_a = packageJson.peerDependencies) === null || _a === void 0 ? void 0 : _a[peerName];
|
|
1046
|
+
const localMap = {
|
|
1047
|
+
'@openwebf/react-core-ui': path_1.default.join('packages', 'react-core-ui'),
|
|
1048
|
+
'@openwebf/vue-core-ui': path_1.default.join('packages', 'vue-core-ui'),
|
|
1049
|
+
};
|
|
1050
|
+
let installSpec = null;
|
|
1051
|
+
const localRel = localMap[peerName];
|
|
1052
|
+
if (localRel) {
|
|
1053
|
+
const localPath = findUp(process.cwd(), localRel);
|
|
1054
|
+
if (localPath) {
|
|
1055
|
+
if (!(0, peerDeps_1.isPackageTypesReady)(localPath)) {
|
|
1056
|
+
const localPkgJsonPath = path_1.default.join(localPath, 'package.json');
|
|
1057
|
+
if (fs_1.default.existsSync(localPkgJsonPath)) {
|
|
1058
|
+
const localPkgJson = (0, peerDeps_1.readJsonFile)(localPkgJsonPath);
|
|
1059
|
+
if ((_b = localPkgJson.scripts) === null || _b === void 0 ? void 0 : _b.build) {
|
|
1060
|
+
if (process.env.WEBF_CODEGEN_BUILD_LOCAL_PEERS !== '1') {
|
|
1061
|
+
console.warn(`\n⚠️ Local ${peerName} found at ${localPath} but type declarations are missing; falling back to registry install.`);
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
console.log(`\n🔧 Local ${peerName} found at ${localPath} but build artifacts are missing; building it for DTS...`);
|
|
1065
|
+
const buildLocalResult = (0, child_process_1.spawnSync)(NPM, ['run', 'build'], {
|
|
1066
|
+
cwd: localPath,
|
|
1067
|
+
stdio: 'inherit'
|
|
1068
|
+
});
|
|
1069
|
+
if (buildLocalResult.status === 0) {
|
|
1070
|
+
if ((0, peerDeps_1.isPackageTypesReady)(localPath)) {
|
|
1071
|
+
installSpec = localPath;
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
console.warn(`\n⚠️ Built local ${peerName} but type declarations are still missing; falling back to registry install.`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
console.warn(`\n⚠️ Failed to build local ${peerName}; falling back to registry install.`);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
installSpec = localPath;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
if (!installSpec) {
|
|
1090
|
+
installSpec = peerRange ? `${peerName}@${peerRange}` : peerName;
|
|
1091
|
+
}
|
|
1092
|
+
console.log(`\n📦 Installing peer dependency for build: ${peerName}...`);
|
|
1093
|
+
const installResult = (0, child_process_1.spawnSync)(NPM, ['install', '--no-save', installSpec], {
|
|
1094
|
+
cwd: packagePath,
|
|
1095
|
+
stdio: 'inherit'
|
|
1096
|
+
});
|
|
1097
|
+
if (installResult.status !== 0) {
|
|
1098
|
+
throw new Error(`Failed to install peer dependency for build: ${peerName}`);
|
|
1099
|
+
}
|
|
1100
|
+
const installedTypesFile = (0, peerDeps_1.getPackageTypesFileFromDir)(getInstalledPackageDir(peerName));
|
|
1101
|
+
if (installedTypesFile && !fs_1.default.existsSync(installedTypesFile)) {
|
|
1102
|
+
throw new Error(`Peer dependency ${peerName} was installed but type declarations were not found at ${installedTypesFile}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1018
1105
|
// Check if node_modules exists
|
|
1019
1106
|
const nodeModulesPath = path_1.default.join(packagePath, 'node_modules');
|
|
1020
1107
|
if (!fs_1.default.existsSync(nodeModulesPath)) {
|
|
@@ -1035,6 +1122,13 @@ function buildPackage(packagePath) {
|
|
|
1035
1122
|
}
|
|
1036
1123
|
// Check if package has a build script
|
|
1037
1124
|
if ((_a = packageJson.scripts) === null || _a === void 0 ? void 0 : _a.build) {
|
|
1125
|
+
// DTS build needs peer deps present locally to resolve types (even though they are not bundled).
|
|
1126
|
+
if ((_b = packageJson.peerDependencies) === null || _b === void 0 ? void 0 : _b['@openwebf/react-core-ui']) {
|
|
1127
|
+
ensurePeerDependencyAvailableForBuild('@openwebf/react-core-ui');
|
|
1128
|
+
}
|
|
1129
|
+
if ((_c = packageJson.peerDependencies) === null || _c === void 0 ? void 0 : _c['@openwebf/vue-core-ui']) {
|
|
1130
|
+
ensurePeerDependencyAvailableForBuild('@openwebf/vue-core-ui');
|
|
1131
|
+
}
|
|
1038
1132
|
console.log(`\nBuilding ${packageName}@${packageVersion}...`);
|
|
1039
1133
|
const buildResult = (0, child_process_1.spawnSync)(NPM, ['run', 'build'], {
|
|
1040
1134
|
cwd: packagePath,
|
package/dist/generator.js
CHANGED
|
@@ -378,11 +378,8 @@ function reactGen(_a) {
|
|
|
378
378
|
(0, logger_1.warn)(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
384
|
-
(0, logger_1.info)('You can now import these components in your React project.');
|
|
385
|
-
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.ts
|
|
381
|
+
// Always generate src/types.ts so generated components can safely import it.
|
|
382
|
+
// When there are no standalone declarations, emit an empty module (`export {};`).
|
|
386
383
|
try {
|
|
387
384
|
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.ConstObject));
|
|
388
385
|
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.EnumObject));
|
|
@@ -395,38 +392,38 @@ function reactGen(_a) {
|
|
|
395
392
|
typeAliases.forEach(t => { if (!typeAliasMap.has(t.name))
|
|
396
393
|
typeAliasMap.set(t.name, t); });
|
|
397
394
|
const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
catch (_b) { }
|
|
395
|
+
const constDecl = Array.from(constMap.values())
|
|
396
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
397
|
+
.join('\n');
|
|
398
|
+
const enumDecl = enums
|
|
399
|
+
.map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
400
|
+
.join('\n');
|
|
401
|
+
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
402
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
403
|
+
.join('\n');
|
|
404
|
+
const typesContent = [
|
|
405
|
+
'/* Generated by WebF CLI - aggregated type declarations */',
|
|
406
|
+
hasAny ? typeAliasDecl : '',
|
|
407
|
+
hasAny ? constDecl : '',
|
|
408
|
+
hasAny ? enumDecl : '',
|
|
409
|
+
hasAny ? '' : 'export {};',
|
|
410
|
+
''
|
|
411
|
+
].filter(Boolean).join('\n');
|
|
412
|
+
const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.ts');
|
|
413
|
+
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
414
|
+
filesChanged++;
|
|
415
|
+
(0, logger_1.debug)(`Generated: src/types.ts`);
|
|
416
|
+
try {
|
|
417
|
+
const constNames = Array.from(constMap.keys());
|
|
418
|
+
const aliasNames = Array.from(typeAliasMap.keys());
|
|
419
|
+
const enumNames = enums.map(e => e.name);
|
|
420
|
+
(0, logger_1.debug)(`[react] Aggregated types - consts: ${constNames.join(', ') || '(none)'}; typeAliases: ${aliasNames.join(', ') || '(none)'}; enums: ${enumNames.join(', ') || '(none)'}\n`);
|
|
421
|
+
(0, logger_1.debug)(`[react] src/types.ts preview:\n` + typesContent.split('\n').slice(0, 20).join('\n'));
|
|
427
422
|
}
|
|
428
|
-
|
|
429
|
-
|
|
423
|
+
catch (_b) { }
|
|
424
|
+
}
|
|
425
|
+
// Only re-export from index.ts when there are actual declarations to surface.
|
|
426
|
+
if (hasAny) {
|
|
430
427
|
try {
|
|
431
428
|
let current = '';
|
|
432
429
|
if (fs_1.default.existsSync(indexFilePath)) {
|
|
@@ -447,6 +444,10 @@ function reactGen(_a) {
|
|
|
447
444
|
catch (e) {
|
|
448
445
|
(0, logger_1.warn)('Failed to generate aggregated React types');
|
|
449
446
|
}
|
|
447
|
+
(0, logger_1.timeEnd)('reactGen');
|
|
448
|
+
(0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
|
|
449
|
+
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
450
|
+
(0, logger_1.info)('You can now import these components in your React project.');
|
|
450
451
|
});
|
|
451
452
|
}
|
|
452
453
|
function vueGen(_a) {
|
package/dist/module.js
CHANGED
|
@@ -247,14 +247,14 @@ function mapTsParamTypeToDart(typeText, optionNames) {
|
|
|
247
247
|
}
|
|
248
248
|
return { dartType: 'dynamic', isByteData: false };
|
|
249
249
|
}
|
|
250
|
-
function mapTsPropertyTypeToDart(type) {
|
|
250
|
+
function mapTsPropertyTypeToDart(type, optional) {
|
|
251
251
|
switch (type.kind) {
|
|
252
252
|
case typescript_1.default.SyntaxKind.StringKeyword:
|
|
253
|
-
return 'String?';
|
|
253
|
+
return optional ? 'String?' : 'String';
|
|
254
254
|
case typescript_1.default.SyntaxKind.NumberKeyword:
|
|
255
|
-
return 'num?';
|
|
255
|
+
return optional ? 'num?' : 'num';
|
|
256
256
|
case typescript_1.default.SyntaxKind.BooleanKeyword:
|
|
257
|
-
return 'bool?';
|
|
257
|
+
return optional ? 'bool?' : 'bool';
|
|
258
258
|
default:
|
|
259
259
|
return 'dynamic';
|
|
260
260
|
}
|
|
@@ -288,28 +288,52 @@ function buildDartBindings(def, command) {
|
|
|
288
288
|
continue;
|
|
289
289
|
const key = member.name.getText(def.sourceFile).replace(/['"]/g, '');
|
|
290
290
|
const fieldName = key;
|
|
291
|
-
const
|
|
292
|
-
|
|
291
|
+
const optional = !!member.questionToken;
|
|
292
|
+
const dartType = member.type ? mapTsPropertyTypeToDart(member.type, optional) : 'dynamic';
|
|
293
|
+
propInfos.push({ fieldName, key, dartType, optional });
|
|
293
294
|
}
|
|
294
295
|
lines.push(`class ${name} {`);
|
|
295
296
|
for (const prop of propInfos) {
|
|
296
297
|
lines.push(` final ${prop.dartType} ${prop.fieldName};`);
|
|
297
298
|
}
|
|
298
299
|
lines.push('');
|
|
299
|
-
const ctorParams = propInfos.map(p =>
|
|
300
|
+
const ctorParams = propInfos.map(p => {
|
|
301
|
+
if (p.optional || p.dartType === 'dynamic') {
|
|
302
|
+
return `this.${p.fieldName}`;
|
|
303
|
+
}
|
|
304
|
+
return `required this.${p.fieldName}`;
|
|
305
|
+
}).join(', ');
|
|
300
306
|
lines.push(` const ${name}({${ctorParams}});`);
|
|
301
307
|
lines.push('');
|
|
302
308
|
lines.push(` factory ${name}.fromMap(Map<String, dynamic> map) {`);
|
|
303
309
|
lines.push(` return ${name}(`);
|
|
304
310
|
for (const prop of propInfos) {
|
|
305
|
-
|
|
306
|
-
|
|
311
|
+
const isString = prop.dartType.startsWith('String');
|
|
312
|
+
const isBool = prop.dartType.startsWith('bool');
|
|
313
|
+
const isNum = prop.dartType.startsWith('num');
|
|
314
|
+
if (isString) {
|
|
315
|
+
if (prop.optional) {
|
|
316
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}']?.toString(),`);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}']?.toString() ?? '',`);
|
|
320
|
+
}
|
|
307
321
|
}
|
|
308
|
-
else if (
|
|
309
|
-
|
|
322
|
+
else if (isBool) {
|
|
323
|
+
if (prop.optional) {
|
|
324
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}'] is bool ? map['${prop.key}'] as bool : null,`);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}'] is bool ? map['${prop.key}'] as bool : false,`);
|
|
328
|
+
}
|
|
310
329
|
}
|
|
311
|
-
else if (
|
|
312
|
-
|
|
330
|
+
else if (isNum) {
|
|
331
|
+
if (prop.optional) {
|
|
332
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}'] is num ? map['${prop.key}'] as num : null,`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
lines.push(` ${prop.fieldName}: map['${prop.key}'] is num ? map['${prop.key}'] as num : 0,`);
|
|
336
|
+
}
|
|
313
337
|
}
|
|
314
338
|
else {
|
|
315
339
|
lines.push(` ${prop.fieldName}: map['${prop.key}'],`);
|
|
@@ -321,7 +345,12 @@ function buildDartBindings(def, command) {
|
|
|
321
345
|
lines.push(' Map<String, dynamic> toMap() {');
|
|
322
346
|
lines.push(' final map = <String, dynamic>{};');
|
|
323
347
|
for (const prop of propInfos) {
|
|
324
|
-
|
|
348
|
+
if (!prop.optional && (prop.dartType === 'String' || prop.dartType === 'bool' || prop.dartType === 'num')) {
|
|
349
|
+
lines.push(` map['${prop.key}'] = ${prop.fieldName};`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
lines.push(` if (${prop.fieldName} != null) { map['${prop.key}'] = ${prop.fieldName}; }`);
|
|
353
|
+
}
|
|
325
354
|
}
|
|
326
355
|
lines.push(' return map;');
|
|
327
356
|
lines.push(' }');
|