@fluffjs/cli 0.3.3 → 0.3.6
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/Cli.js +23 -4
- package/ComponentCompiler.js +2 -0
- package/DecoratorValidator.d.ts +10 -0
- package/DecoratorValidator.js +94 -0
- package/Generator.js +21 -7
- package/fluff-esbuild-plugin.js +2 -0
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/interfaces/GeneratorOptions.d.ts +2 -0
- package/package.json +1 -1
package/Cli.js
CHANGED
|
@@ -79,9 +79,13 @@ Options:
|
|
|
79
79
|
--cwd <dir> Set working directory
|
|
80
80
|
--no-gzip Disable gzip compression (overrides config)
|
|
81
81
|
|
|
82
|
+
Generate Options:
|
|
83
|
+
--packageManager, -p Package manager to use (npm, yarn, pnpm, bun)
|
|
84
|
+
|
|
82
85
|
Examples:
|
|
83
86
|
fluff init Create fluff.json with default configuration
|
|
84
87
|
fluff generate my-app Create a new Fluff app called 'my-app'
|
|
88
|
+
fluff generate my-app --packageManager pnpm Create app and install with pnpm
|
|
85
89
|
fluff build Build the default target
|
|
86
90
|
fluff build app Build the 'app' target
|
|
87
91
|
fluff --nx @myorg/app build Build an nx package
|
|
@@ -263,16 +267,31 @@ Examples:
|
|
|
263
267
|
console.log(`Configuration saved to ${configPath}`);
|
|
264
268
|
}
|
|
265
269
|
generate(args) {
|
|
266
|
-
|
|
270
|
+
let appName = undefined;
|
|
271
|
+
let packageManager = undefined;
|
|
272
|
+
for (let i = 0; i < args.length; i++) {
|
|
273
|
+
const arg = args[i];
|
|
274
|
+
if (arg === '--packageManager' || arg === '-p') {
|
|
275
|
+
const nextArg = args[i + 1];
|
|
276
|
+
if (nextArg === 'npm' || nextArg === 'yarn' || nextArg === 'pnpm' || nextArg === 'bun') {
|
|
277
|
+
packageManager = nextArg;
|
|
278
|
+
i++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else if (!arg.startsWith('-')) {
|
|
282
|
+
appName = arg;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
267
285
|
if (!appName) {
|
|
268
|
-
console.error('Usage: fluff generate <app-name>');
|
|
269
|
-
console.error('Example: fluff generate my-app');
|
|
286
|
+
console.error('Usage: fluff generate <app-name> [--packageManager npm|yarn|pnpm|bun]');
|
|
287
|
+
console.error('Example: fluff generate my-app --packageManager npm');
|
|
270
288
|
process.exit(1);
|
|
271
289
|
}
|
|
272
290
|
const generator = new Generator();
|
|
273
291
|
generator.generate({
|
|
274
292
|
appName,
|
|
275
|
-
outputDir: this.cwd
|
|
293
|
+
outputDir: this.cwd,
|
|
294
|
+
packageManager
|
|
276
295
|
});
|
|
277
296
|
}
|
|
278
297
|
async build(args) {
|
package/ComponentCompiler.js
CHANGED
|
@@ -13,6 +13,7 @@ import importsPlugin from './babel-plugin-imports.js';
|
|
|
13
13
|
import reactivePlugin, { reactivePropertiesMap } from './babel-plugin-reactive.js';
|
|
14
14
|
import { generate } from './BabelHelpers.js';
|
|
15
15
|
import { CodeGenerator } from './CodeGenerator.js';
|
|
16
|
+
import { DecoratorValidator } from './DecoratorValidator.js';
|
|
16
17
|
import { ErrorHelpers } from './ErrorHelpers.js';
|
|
17
18
|
import { GetterDependencyExtractor } from './GetterDependencyExtractor.js';
|
|
18
19
|
import { Parse5Helpers } from './Parse5Helpers.js';
|
|
@@ -154,6 +155,7 @@ export class ComponentCompiler {
|
|
|
154
155
|
if (!skipDefine) {
|
|
155
156
|
result = this.addCustomElementsDefine(result, selector, className);
|
|
156
157
|
}
|
|
158
|
+
DecoratorValidator.validate(result, filePath);
|
|
157
159
|
const tsResult = await this.stripTypeScriptWithSourceMap(result, filePath, sourcemap);
|
|
158
160
|
const watchFiles = [];
|
|
159
161
|
if (templatePath) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class DecoratorValidator {
|
|
2
|
+
static validate(code: string, filePath: string): void;
|
|
3
|
+
private static findDecorators;
|
|
4
|
+
private static checkNode;
|
|
5
|
+
private static checkClassDeclaration;
|
|
6
|
+
private static checkClassMember;
|
|
7
|
+
private static getMemberName;
|
|
8
|
+
private static getDecoratorName;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=DecoratorValidator.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
export class DecoratorValidator {
|
|
4
|
+
static validate(code, filePath) {
|
|
5
|
+
const decorators = DecoratorValidator.findDecorators(code);
|
|
6
|
+
if (decorators.length > 0) {
|
|
7
|
+
const details = decorators.map(d => ` - @${d.name} on ${d.target} '${d.targetName}' at line ${d.line}, column ${d.column}`).join('\n');
|
|
8
|
+
throw new Error(`Decorators must be stripped before output but ${decorators.length} decorator(s) remain in ${filePath}:\n${details}`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
static findDecorators(code) {
|
|
12
|
+
const results = [];
|
|
13
|
+
let ast = null;
|
|
14
|
+
try {
|
|
15
|
+
ast = parse(code, {
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
plugins: ['typescript', 'decorators']
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return results;
|
|
22
|
+
}
|
|
23
|
+
if (!ast) {
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
for (const node of ast.program.body) {
|
|
27
|
+
DecoratorValidator.checkNode(node, results);
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
static checkNode(node, results) {
|
|
32
|
+
if (t.isExportNamedDeclaration(node) && node.declaration) {
|
|
33
|
+
DecoratorValidator.checkNode(node.declaration, results);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (t.isExportDefaultDeclaration(node) && t.isClassDeclaration(node.declaration)) {
|
|
37
|
+
DecoratorValidator.checkClassDeclaration(node.declaration, results);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (t.isClassDeclaration(node)) {
|
|
41
|
+
DecoratorValidator.checkClassDeclaration(node, results);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
static checkClassDeclaration(node, results) {
|
|
45
|
+
const className = node.id?.name ?? '<anonymous>';
|
|
46
|
+
if (node.decorators && node.decorators.length > 0) {
|
|
47
|
+
for (const decorator of node.decorators) {
|
|
48
|
+
results.push({
|
|
49
|
+
name: DecoratorValidator.getDecoratorName(decorator),
|
|
50
|
+
line: decorator.loc?.start.line ?? 0,
|
|
51
|
+
column: decorator.loc?.start.column ?? 0,
|
|
52
|
+
target: 'class',
|
|
53
|
+
targetName: className
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const member of node.body.body) {
|
|
58
|
+
DecoratorValidator.checkClassMember(member, className, results);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
static checkClassMember(member, className, results) {
|
|
62
|
+
if (!('decorators' in member) || !member.decorators || member.decorators.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const memberName = DecoratorValidator.getMemberName(member);
|
|
66
|
+
const target = t.isClassMethod(member) ? 'method' : 'property';
|
|
67
|
+
for (const decorator of member.decorators) {
|
|
68
|
+
results.push({
|
|
69
|
+
name: DecoratorValidator.getDecoratorName(decorator),
|
|
70
|
+
line: decorator.loc?.start.line ?? 0,
|
|
71
|
+
column: decorator.loc?.start.column ?? 0,
|
|
72
|
+
target,
|
|
73
|
+
targetName: `${className}.${memberName}`
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
static getMemberName(member) {
|
|
78
|
+
if ('key' in member && t.isIdentifier(member.key)) {
|
|
79
|
+
return member.key.name;
|
|
80
|
+
}
|
|
81
|
+
return '<unknown>';
|
|
82
|
+
}
|
|
83
|
+
static getDecoratorName(decorator) {
|
|
84
|
+
if (t.isCallExpression(decorator.expression)) {
|
|
85
|
+
if (t.isIdentifier(decorator.expression.callee)) {
|
|
86
|
+
return decorator.expression.callee.name;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (t.isIdentifier(decorator.expression)) {
|
|
90
|
+
return decorator.expression.name;
|
|
91
|
+
}
|
|
92
|
+
return '<unknown>';
|
|
93
|
+
}
|
|
94
|
+
}
|
package/Generator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
2
3
|
import * as fs from 'fs';
|
|
3
4
|
import * as parse5 from 'parse5';
|
|
4
5
|
import * as path from 'path';
|
|
@@ -35,13 +36,26 @@ export class Generator {
|
|
|
35
36
|
console.log(` ✓ Created src/app/${kebabName}.component.ts`);
|
|
36
37
|
console.log(` ✓ Created src/app/${kebabName}.component.html`);
|
|
37
38
|
console.log(` ✓ Created src/app/${kebabName}.component.css`);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
if (options.packageManager) {
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(` Installing dependencies with ${options.packageManager}...`);
|
|
42
|
+
execSync(`${options.packageManager} install`, { cwd: appDir, stdio: 'inherit' });
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log('✅ App created successfully!');
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('Next steps:');
|
|
47
|
+
console.log(` cd ${kebabName}`);
|
|
48
|
+
console.log(' npx @fluffjs/cli serve');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log('✅ App created successfully!');
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('Next steps:');
|
|
55
|
+
console.log(` cd ${kebabName}`);
|
|
56
|
+
console.log(' npm install');
|
|
57
|
+
console.log(' npx @fluffjs/cli serve');
|
|
58
|
+
}
|
|
45
59
|
}
|
|
46
60
|
writeFile(filePath, content) {
|
|
47
61
|
if (fs.existsSync(filePath)) {
|
package/fluff-esbuild-plugin.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as t from '@babel/types';
|
|
|
6
6
|
import { generate } from './BabelHelpers.js';
|
|
7
7
|
import { CodeGenerator } from './CodeGenerator.js';
|
|
8
8
|
import { ComponentCompiler } from './ComponentCompiler.js';
|
|
9
|
+
import { DecoratorValidator } from './DecoratorValidator.js';
|
|
9
10
|
const VIRTUAL_EXPR_TABLE_ID = '@fluff/expr-table';
|
|
10
11
|
function findFluffSourcePath() {
|
|
11
12
|
const thisFile = fileURLToPath(import.meta.url);
|
|
@@ -121,6 +122,7 @@ export function fluffPlugin(options) {
|
|
|
121
122
|
}
|
|
122
123
|
if (decorators.hasPipe) {
|
|
123
124
|
const transformed = await compiler.transformPipeDecorators(source, args.path);
|
|
125
|
+
DecoratorValidator.validate(transformed, args.path);
|
|
124
126
|
return {
|
|
125
127
|
contents: transformed,
|
|
126
128
|
loader: 'ts',
|
package/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { ComponentCompiler } from './ComponentCompiler.js';
|
|
|
2
2
|
export { TemplateParser } from './TemplateParser.js';
|
|
3
3
|
export { CodeGenerator } from './CodeGenerator.js';
|
|
4
4
|
export { Cli } from './Cli.js';
|
|
5
|
+
export { Generator } from './Generator.js';
|
|
6
|
+
export type { PackageManager } from './interfaces/GeneratorOptions.js';
|
|
5
7
|
export type { ComponentInfo } from './interfaces/ComponentInfo.js';
|
|
6
8
|
export type { CompilerOptions } from './interfaces/CompilerOptions.js';
|
|
7
9
|
export type { ControlFlow, SwitchCaseOld } from './interfaces/ControlFlow.js';
|
package/index.js
CHANGED