@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.
- package/.prettierrc +9 -9
- package/bun.lock +680 -0
- package/dist/index.js +310 -460
- package/package.json +55 -54
- package/rollup.config.js +22 -22
- package/src/check-health.ts +53 -54
- package/src/index.ts +53 -61
- package/src/logger.ts +49 -46
- package/src/plugin-api.ts +77 -263
- package/src/query-parser.ts +83 -89
- package/src/static-embed.ts +274 -281
- package/src/transpiler.ts +303 -334
- package/src/version-control.ts +53 -31
- package/tsconfig.json +23 -23
- package/pnpm-workspace.yaml +0 -2
package/src/static-embed.ts
CHANGED
|
@@ -1,281 +1,274 @@
|
|
|
1
|
-
import * as parser from '@babel/parser';
|
|
2
|
-
import { createFilter } from '@rollup/pluginutils';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default function constSysfsExpr(options: EmbedPluginOptions = {}):
|
|
37
|
-
const filter = createFilter(options.include, options.exclude);
|
|
38
|
-
const pluginName = 'millennium-const-sysfs-expr';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
else if (keyName === '
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
valueNode.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
else
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
else if (keyName === '
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
valueNode.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
fs.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
+
}
|