@steambrew/ttc 2.8.7 → 3.0.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,281 +1,274 @@
1
- import * as parser from '@babel/parser';
2
- import { createFilter } from '@rollup/pluginutils';
3
- import chalk from 'chalk';
4
- import fs from 'fs';
5
- import * as glob from 'glob';
6
- import MagicString from 'magic-string';
7
- import path from 'path';
8
- import { Plugin, SourceDescription, TransformPluginContext } from 'rollup';
9
-
10
- // Stupid fix because @babel/traverse exports a CommonJS module
11
- import _traverse from '@babel/traverse';
12
- const traverse = (_traverse as any).default as typeof _traverse;
13
-
14
- interface EmbedPluginOptions {
15
- include?: string | RegExp | (string | RegExp)[];
16
- exclude?: string | RegExp | (string | RegExp)[];
17
- encoding?: BufferEncoding;
18
- }
19
-
20
- interface CallOptions {
21
- basePath?: string;
22
- include?: string;
23
- encoding?: BufferEncoding;
24
- }
25
-
26
- interface FileInfo {
27
- content: string;
28
- filePath: string;
29
- fileName: string;
30
- }
31
-
32
- const Log = (...message: any) => {
33
- console.log(chalk.blueBright.bold('constSysfsExpr'), ...message);
34
- };
35
-
36
- export default function constSysfsExpr(options: EmbedPluginOptions = {}): Plugin {
37
- const filter = createFilter(options.include, options.exclude);
38
- const pluginName = 'millennium-const-sysfs-expr';
39
-
40
- return {
41
- name: pluginName,
42
-
43
- transform(this: TransformPluginContext, code: string, id: string): SourceDescription | null {
44
- if (!filter(id)) return null;
45
- if (!code.includes('constSysfsExpr')) return null;
46
-
47
- const magicString = new MagicString(code);
48
- let hasReplaced = false;
49
-
50
- try {
51
- const stringVariables = new Map<string, string>();
52
-
53
- const ast = parser.parse(code, {
54
- sourceType: 'module',
55
- plugins: ['typescript', 'jsx', 'objectRestSpread', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
56
- });
57
-
58
- traverse(ast, {
59
- VariableDeclarator(path) {
60
- const init = path.node.init;
61
- const id = path.node.id;
62
- if (id.type === 'Identifier' && init && init.type === 'StringLiteral') {
63
- stringVariables.set(id.name, init.value);
64
- }
65
- },
66
- });
67
-
68
- traverse(ast, {
69
- CallExpression: (nodePath) => {
70
- const node = nodePath.node;
71
- if (node.callee.type === 'Identifier' && node.callee.name === 'constSysfsExpr') {
72
- if (typeof node.start !== 'number' || typeof node.end !== 'number') {
73
- if (node.loc) {
74
- this.warn(`Missing start/end offset info for constSysfsExpr call.`, node.loc.start.index);
75
- }
76
- return;
77
- }
78
-
79
- const args = node.arguments;
80
- let pathOrPattern: string | null = null;
81
- const callOptions: Required<Omit<CallOptions, 'fileName'>> = {
82
- basePath: '',
83
- include: '**/*',
84
- encoding: options.encoding || 'utf8',
85
- };
86
-
87
- if (args.length >= 1 && (args[0].type === 'StringLiteral' || args[0].type === 'Identifier')) {
88
- const firstArg = args[0];
89
- if (firstArg.type === 'StringLiteral') {
90
- pathOrPattern = firstArg.value;
91
- } else if (firstArg.type === 'Identifier') {
92
- const varName = firstArg.name;
93
- if (stringVariables.has(varName)) {
94
- pathOrPattern = stringVariables.get(varName) || null;
95
- } else {
96
- this.warn(
97
- `Unable to resolve variable "${varName}" for constSysfsExpr path/pattern. Only simple string literal assignments are supported.`,
98
- firstArg.loc?.start.index,
99
- );
100
- return;
101
- }
102
- }
103
-
104
- if (args.length > 1 && args[1].type === 'ObjectExpression') {
105
- const optionsObj = args[1];
106
- for (const prop of optionsObj.properties) {
107
- if (prop.type !== 'ObjectProperty') continue;
108
- let keyName: string | undefined;
109
- if (prop.key.type === 'Identifier') keyName = prop.key.name;
110
- else if (prop.key.type === 'StringLiteral') keyName = prop.key.value;
111
- else continue;
112
-
113
- if (!['basePath', 'include', 'encoding'].includes(keyName)) continue;
114
-
115
- const valueNode = prop.value;
116
- if (valueNode.type === 'StringLiteral') {
117
- const value = (valueNode as any).extra?.rawValue !== undefined ? (valueNode as any).extra.rawValue : valueNode.value;
118
- if (keyName === 'basePath') callOptions.basePath = value;
119
- else if (keyName === 'include') callOptions.include = value;
120
- else if (keyName === 'encoding') callOptions.encoding = value as BufferEncoding;
121
- } else {
122
- this.warn(
123
- `Option "${keyName}" for constSysfsExpr must be a string literal. Found type: ${valueNode.type}`,
124
- valueNode.loc?.start.index,
125
- );
126
- }
127
- }
128
- }
129
- } else if (args.length >= 1 && args[0].type === 'ObjectExpression') {
130
- const optionsObj = args[0];
131
- for (const prop of optionsObj.properties) {
132
- if (prop.type !== 'ObjectProperty') continue;
133
- let keyName: string | undefined;
134
- if (prop.key.type === 'Identifier') keyName = prop.key.name;
135
- else if (prop.key.type === 'StringLiteral') keyName = prop.key.value;
136
- else continue;
137
-
138
- // In this case, we need to look for 'basePath' and 'include' within the options object itself
139
- if (!['basePath', 'include', 'encoding'].includes(keyName)) continue;
140
-
141
- const valueNode = prop.value;
142
- if (valueNode.type === 'StringLiteral') {
143
- const value = (valueNode as any).extra?.rawValue !== undefined ? (valueNode as any).extra.rawValue : valueNode.value;
144
- if (keyName === 'basePath') callOptions.basePath = value;
145
- else if (keyName === 'include') callOptions.include = value;
146
- else if (keyName === 'encoding') callOptions.encoding = value as BufferEncoding;
147
- } else {
148
- this.warn(
149
- `Option "${keyName}" for constSysfsExpr must be a string literal. Found type: ${valueNode.type}`,
150
- valueNode.loc?.start.index,
151
- );
152
- }
153
- }
154
-
155
- if (callOptions.include !== '**/*') {
156
- pathOrPattern = callOptions.include;
157
- } else {
158
- if (!callOptions.basePath) {
159
- this.warn(
160
- `constSysfsExpr called with only an options object requires at least 'include' or 'basePath' for a pattern.`,
161
- node.loc?.start.index,
162
- );
163
- return;
164
- }
165
- this.warn(`constSysfsExpr called with only an options object requires an explicit 'include' pattern.`, node.loc?.start.index);
166
- return;
167
- }
168
- } else {
169
- this.warn(`constSysfsExpr requires a path/pattern string/variable or an options object as the first argument.`, node.loc?.start.index);
170
- return;
171
- }
172
-
173
- if (!pathOrPattern) {
174
- this.warn(`Invalid or unresolved path/pattern argument for constSysfsExpr.`, args[0]?.loc?.start.index);
175
- return;
176
- }
177
-
178
- try {
179
- const currentLocString = node.loc?.start ? ` at ${id}:${node.loc.start.line}:${node.loc.start.column}` : ` in ${id}`;
180
-
181
- const searchBasePath = callOptions.basePath
182
- ? path.isAbsolute(callOptions.basePath)
183
- ? callOptions.basePath
184
- : path.resolve(path.dirname(id), callOptions.basePath)
185
- : path.isAbsolute(pathOrPattern) && !/[?*+!@()[\]{}]/.test(pathOrPattern)
186
- ? path.dirname(pathOrPattern)
187
- : path.resolve(path.dirname(id), path.dirname(pathOrPattern));
188
-
189
- let embeddedContent: string;
190
- let embeddedCount = 0;
191
-
192
- const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
193
-
194
- if (
195
- !isPotentialPattern &&
196
- fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
197
- fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()
198
- ) {
199
- const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
200
- Log(`Mode: Single file (first argument "${pathOrPattern}" resolved to "${singleFilePath}" relative to "${searchBasePath}")`);
201
-
202
- try {
203
- const rawContent: string | Buffer = fs.readFileSync(singleFilePath, callOptions.encoding);
204
- const contentString = rawContent.toString();
205
- const fileInfo: FileInfo = {
206
- content: contentString,
207
- filePath: singleFilePath,
208
- fileName: path.relative(searchBasePath, singleFilePath),
209
- };
210
- embeddedContent = JSON.stringify(fileInfo);
211
- embeddedCount = 1;
212
- Log(`Embedded 1 specific file for call${currentLocString}`);
213
- } catch (fileError: unknown) {
214
- let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
215
- this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
216
- return;
217
- }
218
- } else {
219
- Log(`Mode: Multi-file (first argument "${pathOrPattern}" is pattern or not a single file)`);
220
-
221
- Log(`Searching with pattern "${pathOrPattern}" in directory "${searchBasePath}" (encoding: ${callOptions.encoding})`);
222
-
223
- const matchingFiles = glob.sync(pathOrPattern, {
224
- cwd: searchBasePath,
225
- nodir: true,
226
- absolute: true,
227
- });
228
-
229
- const fileInfoArray: FileInfo[] = [];
230
- for (const fullPath of matchingFiles) {
231
- try {
232
- const rawContent: string | Buffer = fs.readFileSync(fullPath, callOptions.encoding);
233
- const contentString = rawContent.toString();
234
- fileInfoArray.push({
235
- content: contentString,
236
- filePath: fullPath,
237
- fileName: path.relative(searchBasePath, fullPath),
238
- });
239
- } catch (fileError: unknown) {
240
- let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
241
- this.warn(`Error reading file ${fullPath}: ${message}`);
242
- }
243
- }
244
- embeddedContent = JSON.stringify(fileInfoArray);
245
- embeddedCount = fileInfoArray.length;
246
- Log(`Embedded ${embeddedCount} file(s) matching pattern for call${currentLocString}`);
247
- }
248
-
249
- // Replace the call expression with the generated content string
250
- magicString.overwrite(node.start, node.end, embeddedContent);
251
- hasReplaced = true;
252
- } catch (error: unknown) {
253
- console.error(`Failed to process files for constSysfsExpr call in ${id}:`, error);
254
- const message = String(error instanceof Error ? error.message : error ?? 'Unknown error during file processing');
255
- this.error(`Could not process files for constSysfsExpr: ${message}`, node.loc?.start.index);
256
- return;
257
- }
258
- }
259
- },
260
- });
261
- } catch (error: unknown) {
262
- console.error(`Error parsing or traversing ${id}:`, error);
263
- const message = String(error instanceof Error ? error.message : error ?? 'Unknown parsing error');
264
- this.error(`Failed to parse ${id}: ${message}`);
265
- return null;
266
- }
267
-
268
- // If no replacements were made, return null
269
- if (!hasReplaced) {
270
- return null;
271
- }
272
-
273
- // Return the modified code and source map
274
- const result: SourceDescription = {
275
- code: magicString.toString(),
276
- map: magicString.generateMap({ hires: true }),
277
- };
278
- return result;
279
- },
280
- };
281
- }
1
+ import * as parser from '@babel/parser';
2
+ import { createFilter } from '@rollup/pluginutils';
3
+ import fs from 'fs';
4
+ import * as glob from 'glob';
5
+ import MagicString from 'magic-string';
6
+ import path from 'path';
7
+ import { Plugin, SourceDescription, TransformPluginContext } from 'rollup';
8
+
9
+ export interface SysfsPlugin {
10
+ plugin: Plugin;
11
+ getCount: () => number;
12
+ }
13
+
14
+ // Stupid fix because @babel/traverse exports a CommonJS module
15
+ import _traverse from '@babel/traverse';
16
+ const traverse = (_traverse as any).default as typeof _traverse;
17
+
18
+ interface EmbedPluginOptions {
19
+ include?: string | RegExp | (string | RegExp)[];
20
+ exclude?: string | RegExp | (string | RegExp)[];
21
+ encoding?: BufferEncoding;
22
+ }
23
+
24
+ interface CallOptions {
25
+ basePath?: string;
26
+ include?: string;
27
+ encoding?: BufferEncoding;
28
+ }
29
+
30
+ interface FileInfo {
31
+ content: string;
32
+ filePath: string;
33
+ fileName: string;
34
+ }
35
+
36
+ export default function constSysfsExpr(options: EmbedPluginOptions = {}): SysfsPlugin {
37
+ const filter = createFilter(options.include, options.exclude);
38
+ const pluginName = 'millennium-const-sysfs-expr';
39
+ let count = 0;
40
+
41
+ const plugin: Plugin = {
42
+ name: pluginName,
43
+
44
+ transform(this: TransformPluginContext, code: string, id: string): SourceDescription | null {
45
+ if (!filter(id)) return null;
46
+ if (!code.includes('constSysfsExpr')) return null;
47
+
48
+ const magicString = new MagicString(code);
49
+ let hasReplaced = false;
50
+
51
+ try {
52
+ const stringVariables = new Map<string, string>();
53
+
54
+ const ast = parser.parse(code, {
55
+ sourceType: 'module',
56
+ plugins: ['typescript', 'jsx', 'objectRestSpread', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
57
+ });
58
+
59
+ traverse(ast, {
60
+ VariableDeclarator(path) {
61
+ const init = path.node.init;
62
+ const id = path.node.id;
63
+ if (id.type === 'Identifier' && init && init.type === 'StringLiteral') {
64
+ stringVariables.set(id.name, init.value);
65
+ }
66
+ },
67
+ });
68
+
69
+ traverse(ast, {
70
+ CallExpression: (nodePath) => {
71
+ const node = nodePath.node;
72
+ if (node.callee.type === 'Identifier' && node.callee.name === 'constSysfsExpr') {
73
+ if (typeof node.start !== 'number' || typeof node.end !== 'number') {
74
+ if (node.loc) {
75
+ this.warn(`Missing start/end offset info for constSysfsExpr call.`, node.loc.start.index);
76
+ }
77
+ return;
78
+ }
79
+
80
+ const args = node.arguments;
81
+ let pathOrPattern: string | null = null;
82
+ const callOptions: Required<Omit<CallOptions, 'fileName'>> = {
83
+ basePath: '',
84
+ include: '**/*',
85
+ encoding: options.encoding || 'utf8',
86
+ };
87
+
88
+ if (args.length >= 1 && (args[0].type === 'StringLiteral' || args[0].type === 'Identifier')) {
89
+ const firstArg = args[0];
90
+ if (firstArg.type === 'StringLiteral') {
91
+ pathOrPattern = firstArg.value;
92
+ } else if (firstArg.type === 'Identifier') {
93
+ const varName = firstArg.name;
94
+ if (stringVariables.has(varName)) {
95
+ pathOrPattern = stringVariables.get(varName) || null;
96
+ } else {
97
+ this.warn(
98
+ `Unable to resolve variable "${varName}" for constSysfsExpr path/pattern. Only simple string literal assignments are supported.`,
99
+ firstArg.loc?.start.index,
100
+ );
101
+ return;
102
+ }
103
+ }
104
+
105
+ if (args.length > 1 && args[1].type === 'ObjectExpression') {
106
+ const optionsObj = args[1];
107
+ for (const prop of optionsObj.properties) {
108
+ if (prop.type !== 'ObjectProperty') continue;
109
+ let keyName: string | undefined;
110
+ if (prop.key.type === 'Identifier') keyName = prop.key.name;
111
+ else if (prop.key.type === 'StringLiteral') keyName = prop.key.value;
112
+ else continue;
113
+
114
+ if (!['basePath', 'include', 'encoding'].includes(keyName)) continue;
115
+
116
+ const valueNode = prop.value;
117
+ if (valueNode.type === 'StringLiteral') {
118
+ const value = (valueNode as any).extra?.rawValue !== undefined ? (valueNode as any).extra.rawValue : valueNode.value;
119
+ if (keyName === 'basePath') callOptions.basePath = value;
120
+ else if (keyName === 'include') callOptions.include = value;
121
+ else if (keyName === 'encoding') callOptions.encoding = value as BufferEncoding;
122
+ } else {
123
+ this.warn(
124
+ `Option "${keyName}" for constSysfsExpr must be a string literal. Found type: ${valueNode.type}`,
125
+ valueNode.loc?.start.index,
126
+ );
127
+ }
128
+ }
129
+ }
130
+ } else if (args.length >= 1 && args[0].type === 'ObjectExpression') {
131
+ const optionsObj = args[0];
132
+ for (const prop of optionsObj.properties) {
133
+ if (prop.type !== 'ObjectProperty') continue;
134
+ let keyName: string | undefined;
135
+ if (prop.key.type === 'Identifier') keyName = prop.key.name;
136
+ else if (prop.key.type === 'StringLiteral') keyName = prop.key.value;
137
+ else continue;
138
+
139
+ // In this case, we need to look for 'basePath' and 'include' within the options object itself
140
+ if (!['basePath', 'include', 'encoding'].includes(keyName)) continue;
141
+
142
+ const valueNode = prop.value;
143
+ if (valueNode.type === 'StringLiteral') {
144
+ const value = (valueNode as any).extra?.rawValue !== undefined ? (valueNode as any).extra.rawValue : valueNode.value;
145
+ if (keyName === 'basePath') callOptions.basePath = value;
146
+ else if (keyName === 'include') callOptions.include = value;
147
+ else if (keyName === 'encoding') callOptions.encoding = value as BufferEncoding;
148
+ } else {
149
+ this.warn(
150
+ `Option "${keyName}" for constSysfsExpr must be a string literal. Found type: ${valueNode.type}`,
151
+ valueNode.loc?.start.index,
152
+ );
153
+ }
154
+ }
155
+
156
+ if (callOptions.include !== '**/*') {
157
+ pathOrPattern = callOptions.include;
158
+ } else {
159
+ if (!callOptions.basePath) {
160
+ this.warn(
161
+ `constSysfsExpr called with only an options object requires at least 'include' or 'basePath' for a pattern.`,
162
+ node.loc?.start.index,
163
+ );
164
+ return;
165
+ }
166
+ this.warn(`constSysfsExpr called with only an options object requires an explicit 'include' pattern.`, node.loc?.start.index);
167
+ return;
168
+ }
169
+ } else {
170
+ this.warn(`constSysfsExpr requires a path/pattern string/variable or an options object as the first argument.`, node.loc?.start.index);
171
+ return;
172
+ }
173
+
174
+ if (!pathOrPattern) {
175
+ this.warn(`Invalid or unresolved path/pattern argument for constSysfsExpr.`, args[0]?.loc?.start.index);
176
+ return;
177
+ }
178
+
179
+ try {
180
+
181
+ const searchBasePath = callOptions.basePath
182
+ ? path.isAbsolute(callOptions.basePath)
183
+ ? callOptions.basePath
184
+ : path.resolve(path.dirname(id), callOptions.basePath)
185
+ : path.isAbsolute(pathOrPattern) && !/[?*+!@()[\]{}]/.test(pathOrPattern)
186
+ ? path.dirname(pathOrPattern)
187
+ : path.resolve(path.dirname(id), path.dirname(pathOrPattern));
188
+
189
+ let embeddedContent: string;
190
+
191
+ const isPotentialPattern = /[?*+!@()[\]{}]/.test(pathOrPattern);
192
+
193
+ if (
194
+ !isPotentialPattern &&
195
+ fs.existsSync(path.resolve(searchBasePath, pathOrPattern)) &&
196
+ fs.statSync(path.resolve(searchBasePath, pathOrPattern)).isFile()
197
+ ) {
198
+ const singleFilePath = path.resolve(searchBasePath, pathOrPattern);
199
+
200
+ try {
201
+ const rawContent: string | Buffer = fs.readFileSync(singleFilePath, callOptions.encoding);
202
+ const contentString = rawContent.toString();
203
+ const fileInfo: FileInfo = {
204
+ content: contentString,
205
+ filePath: singleFilePath,
206
+ fileName: path.relative(searchBasePath, singleFilePath),
207
+ };
208
+ embeddedContent = JSON.stringify(fileInfo);
209
+ } catch (fileError: unknown) {
210
+ let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
211
+ this.error(`Error reading file ${singleFilePath}: ${message}`, node.loc?.start.index);
212
+ return;
213
+ }
214
+ } else {
215
+
216
+
217
+ const matchingFiles = glob.sync(pathOrPattern, {
218
+ cwd: searchBasePath,
219
+ nodir: true,
220
+ absolute: true,
221
+ });
222
+
223
+ const fileInfoArray: FileInfo[] = [];
224
+ for (const fullPath of matchingFiles) {
225
+ try {
226
+ const rawContent: string | Buffer = fs.readFileSync(fullPath, callOptions.encoding);
227
+ const contentString = rawContent.toString();
228
+ fileInfoArray.push({
229
+ content: contentString,
230
+ filePath: fullPath,
231
+ fileName: path.relative(searchBasePath, fullPath),
232
+ });
233
+ } catch (fileError: unknown) {
234
+ let message = String(fileError instanceof Error ? fileError.message : fileError ?? 'Unknown file read error');
235
+ this.warn(`Error reading file ${fullPath}: ${message}`);
236
+ }
237
+ }
238
+ embeddedContent = JSON.stringify(fileInfoArray);
239
+ }
240
+
241
+ // Replace the call expression with the generated content string
242
+ magicString.overwrite(node.start, node.end, embeddedContent);
243
+ hasReplaced = true;
244
+ count++;
245
+ } catch (error: unknown) {
246
+ const message = String(error instanceof Error ? error.message : error ?? 'Unknown error during file processing');
247
+ this.error(`Could not process files for constSysfsExpr: ${message}`, node.loc?.start.index);
248
+ return;
249
+ }
250
+ }
251
+ },
252
+ });
253
+ } catch (error: unknown) {
254
+ const message = String(error instanceof Error ? error.message : error ?? 'Unknown parsing error');
255
+ this.error(`Failed to parse ${id}: ${message}`);
256
+ return null;
257
+ }
258
+
259
+ // If no replacements were made, return null
260
+ if (!hasReplaced) {
261
+ return null;
262
+ }
263
+
264
+ // Return the modified code and source map
265
+ const result: SourceDescription = {
266
+ code: magicString.toString(),
267
+ map: magicString.generateMap({ hires: true }),
268
+ };
269
+ return result;
270
+ },
271
+ };
272
+
273
+ return { plugin, getCount: () => count };
274
+ }