@ng-prism/core 21.6.1 → 21.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/schematics/ng-add/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,IAAI,EAIV,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAqOtD,wBAAgB,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAOvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/schematics/ng-add/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,IAAI,EAIV,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA6RtD,wBAAgB,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAUvD"}
@@ -173,11 +173,62 @@ function createConfigFile() {
173
173
  return tree;
174
174
  };
175
175
  }
176
+ function addGitignoreEntry(options) {
177
+ return (tree) => {
178
+ const prismProjectName = `${options.project}-prism`;
179
+ const entry = `projects/${prismProjectName}/src/prism-manifest.ts`;
180
+ const gitignorePath = '.gitignore';
181
+ const buffer = tree.read(gitignorePath);
182
+ if (buffer) {
183
+ const content = buffer.toString('utf-8');
184
+ if (content.includes(entry))
185
+ return tree;
186
+ tree.overwrite(gitignorePath, content.trimEnd() + '\n' + entry + '\n');
187
+ }
188
+ else {
189
+ tree.create(gitignorePath, entry + '\n');
190
+ }
191
+ return tree;
192
+ };
193
+ }
194
+ function logSetupSummary(options) {
195
+ return (_tree, context) => {
196
+ const prismProjectName = `${options.project}-prism`;
197
+ context.logger.info('');
198
+ context.logger.info('ng-prism setup complete:');
199
+ context.logger.info(` Prism app project: ${prismProjectName}`);
200
+ context.logger.info(` Config file: ng-prism.config.ts`);
201
+ context.logger.info(` Dev server: ng run ${options.project}:prism`);
202
+ context.logger.info(` Production build: ng run ${options.project}:prism-build`);
203
+ context.logger.info(` Strip decorators: npm run strip-showcase`);
204
+ context.logger.info('');
205
+ };
206
+ }
207
+ function addStripShowcaseScript(options) {
208
+ return (tree) => {
209
+ const pkgPath = 'package.json';
210
+ const buffer = tree.read(pkgPath);
211
+ if (!buffer)
212
+ return tree;
213
+ const pkg = JSON.parse(buffer.toString('utf-8'));
214
+ if (!pkg.scripts)
215
+ pkg.scripts = {};
216
+ const scriptName = 'strip-showcase';
217
+ if (pkg.scripts[scriptName])
218
+ return tree;
219
+ pkg.scripts[scriptName] = `ng-prism-strip dist/${options.project}`;
220
+ tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
221
+ return tree;
222
+ };
223
+ }
176
224
  export function ngAdd(options) {
177
225
  return chain([
178
226
  addPrismAppProject(options),
179
227
  addBuilderTargets(options),
180
228
  addTsConfigPaths(options),
181
229
  createConfigFile(),
230
+ addStripShowcaseScript(options),
231
+ addGitignoreEntry(options),
232
+ logSetupSummary(options),
182
233
  ]);
183
234
  }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/transformer/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { stripShowcaseFromDirectory } from './strip-directory.js';
3
+ const dir = process.argv[2];
4
+ if (!dir) {
5
+ console.error('Usage: ng-prism-strip <dist-dir>');
6
+ process.exit(1);
7
+ }
8
+ stripShowcaseFromDirectory(dir).then((result) => {
9
+ console.log(`Stripped @Showcase from ${result.strippedFiles} of ${result.totalFiles} files.`);
10
+ }, (error) => {
11
+ console.error(`Error: ${error.message}`);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,3 @@
1
+ export { createShowcaseStripTransformer, stripShowcaseDecorators, } from './showcase-strip.transformer.js';
2
+ export { stripShowcaseFromDirectory, type StripResult, } from './strip-directory.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transformer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,0BAA0B,EAC1B,KAAK,WAAW,GACjB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createShowcaseStripTransformer, stripShowcaseDecorators, } from './showcase-strip.transformer.js';
2
+ export { stripShowcaseFromDirectory, } from './strip-directory.js';
@@ -0,0 +1,4 @@
1
+ import ts from 'typescript';
2
+ export declare function createShowcaseStripTransformer(): ts.TransformerFactory<ts.SourceFile>;
3
+ export declare function stripShowcaseDecorators(source: string, fileName?: string): string;
4
+ //# sourceMappingURL=showcase-strip.transformer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"showcase-strip.transformer.d.ts","sourceRoot":"","sources":["../../src/transformer/showcase-strip.transformer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAmK5B,wBAAgB,8BAA8B,IAAI,EAAE,CAAC,kBAAkB,CAAC,EAAE,CAAC,UAAU,CAAC,CAuCrF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM,CAWpF"}
@@ -0,0 +1,147 @@
1
+ import ts from 'typescript';
2
+ const SHOWCASE_MODULE_PREFIXES = ['@ng-prism/core', 'ng-prism'];
3
+ function isShowcaseModule(specifier) {
4
+ return SHOWCASE_MODULE_PREFIXES.some((prefix) => specifier === prefix || specifier.startsWith(prefix + '/'));
5
+ }
6
+ function isShowcaseCall(expr, names) {
7
+ return ts.isCallExpression(expr)
8
+ && ts.isIdentifier(expr.expression)
9
+ && names.has(expr.expression.text);
10
+ }
11
+ function isLoweredShowcaseCall(expr, names) {
12
+ if (!ts.isCallExpression(expr))
13
+ return false;
14
+ return isShowcaseCall(expr.expression, names);
15
+ }
16
+ function tryHandleDecorateStatement(stmt, names, factory) {
17
+ const expr = stmt.expression;
18
+ if (!ts.isBinaryExpression(expr) || expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) {
19
+ return undefined;
20
+ }
21
+ const rhs = expr.right;
22
+ if (!ts.isCallExpression(rhs) || !ts.isIdentifier(rhs.expression) || rhs.expression.text !== '__decorate') {
23
+ return undefined;
24
+ }
25
+ const args = rhs.arguments;
26
+ if (args.length < 2 || !ts.isArrayLiteralExpression(args[0])) {
27
+ return undefined;
28
+ }
29
+ const decoratorArray = args[0];
30
+ const hasShowcase = decoratorArray.elements.some((el) => isShowcaseCall(el, names));
31
+ if (!hasShowcase)
32
+ return undefined;
33
+ const remaining = decoratorArray.elements.filter((el) => !isShowcaseCall(el, names));
34
+ if (remaining.length === 0) {
35
+ return { action: 'remove' };
36
+ }
37
+ const newArray = factory.updateArrayLiteralExpression(decoratorArray, remaining);
38
+ const newCall = factory.updateCallExpression(rhs, rhs.expression, rhs.typeArguments, [newArray, ...args.slice(1)]);
39
+ const newBinary = factory.updateBinaryExpression(expr, expr.left, expr.operatorToken, newCall);
40
+ const newStmt = factory.updateExpressionStatement(stmt, newBinary);
41
+ return { action: 'replace', statement: newStmt };
42
+ }
43
+ function isShowcaseDecorator(d, names) {
44
+ return ts.isCallExpression(d.expression)
45
+ && ts.isIdentifier(d.expression.expression)
46
+ && names.has(d.expression.expression.text);
47
+ }
48
+ function stripNativeDecorators(node, names, factory) {
49
+ const decorators = ts.getDecorators(node);
50
+ if (!decorators)
51
+ return undefined;
52
+ const keptDecorators = decorators.filter((d) => !isShowcaseDecorator(d, names));
53
+ if (keptDecorators.length === decorators.length)
54
+ return undefined;
55
+ const modifiers = ts.getModifiers(node) ?? [];
56
+ return factory.updateClassDeclaration(node, [...keptDecorators, ...modifiers], node.name, node.typeParameters, node.heritageClauses, node.members);
57
+ }
58
+ function findShowcaseImports(sourceFile) {
59
+ const localNames = new Set();
60
+ const declarations = new Set();
61
+ for (const stmt of sourceFile.statements) {
62
+ if (!ts.isImportDeclaration(stmt))
63
+ continue;
64
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
65
+ continue;
66
+ if (!isShowcaseModule(stmt.moduleSpecifier.text))
67
+ continue;
68
+ const namedBindings = stmt.importClause?.namedBindings;
69
+ if (!namedBindings || !ts.isNamedImports(namedBindings))
70
+ continue;
71
+ for (const spec of namedBindings.elements) {
72
+ const importedName = (spec.propertyName ?? spec.name).text;
73
+ if (importedName === 'Showcase') {
74
+ localNames.add(spec.name.text);
75
+ declarations.add(stmt);
76
+ }
77
+ }
78
+ }
79
+ return { localNames, declarations };
80
+ }
81
+ function cleanupImports(statements, info, factory) {
82
+ return statements.reduce((acc, stmt) => {
83
+ if (!ts.isImportDeclaration(stmt) || !info.declarations.has(stmt)) {
84
+ acc.push(stmt);
85
+ return acc;
86
+ }
87
+ const namedBindings = stmt.importClause?.namedBindings;
88
+ if (!namedBindings || !ts.isNamedImports(namedBindings)) {
89
+ acc.push(stmt);
90
+ return acc;
91
+ }
92
+ const remaining = namedBindings.elements.filter((spec) => {
93
+ const importedName = (spec.propertyName ?? spec.name).text;
94
+ return importedName !== 'Showcase';
95
+ });
96
+ if (remaining.length === 0)
97
+ return acc;
98
+ acc.push(factory.updateImportDeclaration(stmt, stmt.modifiers, factory.updateImportClause(stmt.importClause, stmt.importClause.isTypeOnly, stmt.importClause.name, factory.updateNamedImports(namedBindings, remaining)), stmt.moduleSpecifier, stmt.attributes));
99
+ return acc;
100
+ }, []);
101
+ }
102
+ export function createShowcaseStripTransformer() {
103
+ return (context) => {
104
+ const factory = context.factory;
105
+ return (sourceFile) => {
106
+ const importInfo = findShowcaseImports(sourceFile);
107
+ if (importInfo.localNames.size === 0)
108
+ return sourceFile;
109
+ const names = importInfo.localNames;
110
+ const filtered = [];
111
+ for (const stmt of sourceFile.statements) {
112
+ if (ts.isExpressionStatement(stmt)) {
113
+ if (isLoweredShowcaseCall(stmt.expression, names))
114
+ continue;
115
+ const decorateResult = tryHandleDecorateStatement(stmt, names, factory);
116
+ if (decorateResult) {
117
+ if (decorateResult.action === 'replace' && decorateResult.statement) {
118
+ filtered.push(decorateResult.statement);
119
+ }
120
+ continue;
121
+ }
122
+ }
123
+ if (ts.isClassDeclaration(stmt)) {
124
+ const stripped = stripNativeDecorators(stmt, names, factory);
125
+ if (stripped) {
126
+ filtered.push(stripped);
127
+ continue;
128
+ }
129
+ }
130
+ filtered.push(stmt);
131
+ }
132
+ const cleaned = cleanupImports(filtered, importInfo, factory);
133
+ return factory.updateSourceFile(sourceFile, cleaned);
134
+ };
135
+ };
136
+ }
137
+ export function stripShowcaseDecorators(source, fileName = 'file.ts') {
138
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
139
+ const importInfo = findShowcaseImports(sourceFile);
140
+ if (importInfo.localNames.size === 0)
141
+ return source;
142
+ const result = ts.transform(sourceFile, [createShowcaseStripTransformer()]);
143
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
144
+ const output = printer.printFile(result.transformed[0]);
145
+ result.dispose();
146
+ return output;
147
+ }
@@ -0,0 +1,6 @@
1
+ export interface StripResult {
2
+ totalFiles: number;
3
+ strippedFiles: number;
4
+ }
5
+ export declare function stripShowcaseFromDirectory(dir: string): Promise<StripResult>;
6
+ //# sourceMappingURL=strip-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strip-directory.d.ts","sourceRoot":"","sources":["../../src/transformer/strip-directory.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAkBlF"}
@@ -0,0 +1,20 @@
1
+ import { readdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join, extname } from 'node:path';
3
+ import { stripShowcaseDecorators } from './showcase-strip.transformer.js';
4
+ const JS_EXTENSIONS = new Set(['.js', '.mjs']);
5
+ export async function stripShowcaseFromDirectory(dir) {
6
+ const entries = await readdir(dir, { recursive: true });
7
+ const files = entries
8
+ .filter((entry) => JS_EXTENSIONS.has(extname(entry)))
9
+ .map((entry) => join(dir, entry));
10
+ let strippedFiles = 0;
11
+ for (const file of files) {
12
+ const source = await readFile(file, 'utf-8');
13
+ const result = stripShowcaseDecorators(source, file);
14
+ if (result !== source) {
15
+ await writeFile(file, result, 'utf-8');
16
+ strippedFiles++;
17
+ }
18
+ }
19
+ return { totalFiles: files.length, strippedFiles };
20
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-prism/core",
3
- "version": "21.6.1",
3
+ "version": "21.7.0",
4
4
  "description": "Lightweight Angular-native component showcase — no story files needed.",
5
5
  "keywords": [
6
6
  "angular",
@@ -30,8 +30,16 @@
30
30
  "types": "./dist/plugin/index.d.ts",
31
31
  "import": "./dist/plugin/index.js",
32
32
  "default": "./dist/plugin/index.js"
33
+ },
34
+ "./transformer": {
35
+ "types": "./dist/transformer/index.d.ts",
36
+ "import": "./dist/transformer/index.js",
37
+ "default": "./dist/transformer/index.js"
33
38
  }
34
39
  },
40
+ "bin": {
41
+ "ng-prism-strip": "./dist/transformer/cli.js"
42
+ },
35
43
  "schematics": "./schematics/collection.json",
36
44
  "builders": "./builders.json",
37
45
  "files": [