@savvy-web/github-action-builder 2.2.3 → 2.2.5
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.
|
@@ -24,7 +24,7 @@ const rootCommand = Command.make("github-action-builder").pipe(Command.withSubco
|
|
|
24
24
|
/**
|
|
25
25
|
* CLI application: reads argv from the Stdio service provided by NodeServices.
|
|
26
26
|
*/
|
|
27
|
-
const cli = Command.run(rootCommand, { version: "2.2.
|
|
27
|
+
const cli = Command.run(rootCommand, { version: "2.2.5" });
|
|
28
28
|
/**
|
|
29
29
|
* Combined layer: AppLayer + the Node implementations of the CLI
|
|
30
30
|
* environment (FileSystem, Path, Terminal, Stdio, ChildProcessSpawner).
|
package/cli/commands/init.js
CHANGED
|
@@ -20,7 +20,7 @@ const forceOption = Flag.boolean("force").pipe(Flag.withAlias("f"), Flag.withDes
|
|
|
20
20
|
* Get current package version (replaced at build time).
|
|
21
21
|
*/
|
|
22
22
|
const getPackageVersion = () => {
|
|
23
|
-
return "2.2.
|
|
23
|
+
return "2.2.5";
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* Generate package.json content.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* rspack (like webpack) respects a `/* webpackIgnore: true *\/` comment
|
|
21
21
|
* immediately inside the `import(` call — it skips context-module analysis
|
|
22
22
|
* for that call and leaves a plain, native, runtime `import()` in the
|
|
23
|
-
* output. This loader is a
|
|
23
|
+
* output. This loader is a string transform (no AST parsing, so no
|
|
24
24
|
* source-map chaining — the returned source is treated as a 1:1 replacement
|
|
25
25
|
* and any pre-existing source map for this file is not remapped) that runs
|
|
26
26
|
* in two passes:
|
|
@@ -43,11 +43,28 @@
|
|
|
43
43
|
* backtick it sees, which is a documented limitation of this non-AST
|
|
44
44
|
* heuristic.
|
|
45
45
|
*
|
|
46
|
+
* Both passes are gated by a lexical scan of their input: any `import(`
|
|
47
|
+
* whose keyword sits inside a line comment, block comment, string literal,
|
|
48
|
+
* template-literal text, or regex literal is left untouched. Without that
|
|
49
|
+
* gate a doc comment that merely *mentions* `import(...)` got the block
|
|
50
|
+
* comment injected into it, and the injected `*\/` closed the enclosing
|
|
51
|
+
* comment early — spilling the comment remainder into the token stream as a
|
|
52
|
+
* syntax error (savvy-web/systems#412). The scan is a lexer, not a parser:
|
|
53
|
+
* regex-vs-division at a `/` is decided by the standard
|
|
54
|
+
* preceding-token heuristic (identifier/`)`/`]` → division, unless the
|
|
55
|
+
* identifier is a keyword like `return` or `typeof` → regex), which can
|
|
56
|
+
* misread pathological code such as `if (x) /re/.test(s)` — acceptable for
|
|
57
|
+
* the bundled-package sources this loader sees.
|
|
58
|
+
*
|
|
46
59
|
* Deliberately skipped (left untouched):
|
|
47
60
|
* - `import("./static.js")` — a string-literal import; the bundler already
|
|
48
61
|
* resolves and bundles these correctly, no need to touch them.
|
|
49
62
|
* - `` import(`./static.js`) `` — a fully static template literal; same
|
|
50
63
|
* reasoning as a plain string (see pass 2 above).
|
|
64
|
+
* - `import()` — an empty argument list is never a resolvable dynamic
|
|
65
|
+
* import, so there is nothing to preserve (this is the prose form doc
|
|
66
|
+
* comments overwhelmingly use, so it is also excluded by the lookahead as
|
|
67
|
+
* defense in depth alongside the lexical gate).
|
|
51
68
|
* - `import(/* webpackIgnore: true *\/ x)` — already has the comment
|
|
52
69
|
* injected (idempotent: running this loader twice must not double-inject).
|
|
53
70
|
* - `important(x)` — the `\b` word-boundary guard on `import` prevents
|
|
@@ -58,19 +75,233 @@
|
|
|
58
75
|
* `public/` in this package's `package.json`, which is copied verbatim to
|
|
59
76
|
* the package root by the build (so this ships at `<pkg>/loaders/
|
|
60
77
|
* webpack-ignore-dynamic-imports.cjs`, not under a `public/` prefix).
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Keywords after which a `/` starts a regex literal rather than division,
|
|
82
|
+
* even though the preceding character is an identifier character.
|
|
83
|
+
*/
|
|
84
|
+
const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
85
|
+
"await",
|
|
86
|
+
"case",
|
|
87
|
+
"delete",
|
|
88
|
+
"do",
|
|
89
|
+
"else",
|
|
90
|
+
"in",
|
|
91
|
+
"instanceof",
|
|
92
|
+
"new",
|
|
93
|
+
"of",
|
|
94
|
+
"return",
|
|
95
|
+
"throw",
|
|
96
|
+
"typeof",
|
|
97
|
+
"void",
|
|
98
|
+
"yield",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const WORD_CHAR = /[$\w]/;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Lexically scans JS source and returns the sorted `[start, end)` ranges of
|
|
105
|
+
* every region where an `import(` occurrence is NOT a dynamic import call:
|
|
106
|
+
* line comments, block comments, string literals, template-literal text
|
|
107
|
+
* (interpolation code is deliberately left unprotected — a real dynamic
|
|
108
|
+
* import can live inside `${ ... }`), and regex literals.
|
|
61
109
|
*
|
|
62
|
-
* @param source - The
|
|
63
|
-
* @returns
|
|
64
|
-
|
|
110
|
+
* @param {string} source - The module source text.
|
|
111
|
+
* @returns {Array<[number, number]>} Sorted, non-overlapping ranges.
|
|
112
|
+
*/
|
|
113
|
+
function computeProtectedRanges(source) {
|
|
114
|
+
const ranges = [];
|
|
115
|
+
// Brace-depth counters for template literals whose `${` interpolation we
|
|
116
|
+
// are currently inside; the top of the stack tracks the innermost one.
|
|
117
|
+
const templateStack = [];
|
|
118
|
+
// Last significant character and trailing identifier word seen in code
|
|
119
|
+
// state — the standard regex-vs-division heuristic input.
|
|
120
|
+
let lastCode = "";
|
|
121
|
+
let lastWord = "";
|
|
122
|
+
const len = source.length;
|
|
123
|
+
let i = 0;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Scans template-literal text starting at the opening backtick or at a
|
|
127
|
+
* `}` resuming an interrupted template. Pushes the protected text range
|
|
128
|
+
* and returns the index to continue from (inside interpolation code or
|
|
129
|
+
* after the closing backtick).
|
|
130
|
+
*
|
|
131
|
+
* @param {number} start - Index of the backtick or resuming `}`.
|
|
132
|
+
* @returns {number} Index to continue scanning from.
|
|
133
|
+
*/
|
|
134
|
+
const scanTemplateText = (start) => {
|
|
135
|
+
let j = start + 1;
|
|
136
|
+
while (j < len) {
|
|
137
|
+
const c = source[j];
|
|
138
|
+
if (c === "\\") {
|
|
139
|
+
j += 2;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (c === "`") {
|
|
143
|
+
ranges.push([start, j + 1]);
|
|
144
|
+
return j + 1;
|
|
145
|
+
}
|
|
146
|
+
if (c === "$" && source[j + 1] === "{") {
|
|
147
|
+
ranges.push([start, j + 2]);
|
|
148
|
+
templateStack.push(0);
|
|
149
|
+
return j + 2;
|
|
150
|
+
}
|
|
151
|
+
j++;
|
|
152
|
+
}
|
|
153
|
+
ranges.push([start, len]);
|
|
154
|
+
return len;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
while (i < len) {
|
|
158
|
+
const ch = source[i];
|
|
159
|
+
const next = source[i + 1];
|
|
160
|
+
if (ch === "/" && next === "/") {
|
|
161
|
+
const start = i;
|
|
162
|
+
i += 2;
|
|
163
|
+
while (i < len && source[i] !== "\n") {
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
ranges.push([start, i]);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (ch === "/" && next === "*") {
|
|
170
|
+
const start = i;
|
|
171
|
+
i += 2;
|
|
172
|
+
while (i < len && !(source[i] === "*" && source[i + 1] === "/")) {
|
|
173
|
+
i++;
|
|
174
|
+
}
|
|
175
|
+
i = Math.min(i + 2, len);
|
|
176
|
+
ranges.push([start, i]);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ch === '"' || ch === "'") {
|
|
180
|
+
const start = i;
|
|
181
|
+
i++;
|
|
182
|
+
while (i < len && source[i] !== ch && source[i] !== "\n") {
|
|
183
|
+
if (source[i] === "\\") {
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
i++;
|
|
189
|
+
ranges.push([start, Math.min(i, len)]);
|
|
190
|
+
lastCode = ")";
|
|
191
|
+
lastWord = "";
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (ch === "`") {
|
|
195
|
+
i = scanTemplateText(i);
|
|
196
|
+
lastCode = ")";
|
|
197
|
+
lastWord = "";
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (ch === "/") {
|
|
201
|
+
// Regex literal vs division: regex unless the previous significant
|
|
202
|
+
// token can end an expression.
|
|
203
|
+
const afterWord = WORD_CHAR.test(lastCode);
|
|
204
|
+
const isDivision =
|
|
205
|
+
lastCode !== "" &&
|
|
206
|
+
((afterWord && !REGEX_PRECEDING_KEYWORDS.has(lastWord)) || lastCode === ")" || lastCode === "]");
|
|
207
|
+
if (isDivision) {
|
|
208
|
+
lastCode = ch;
|
|
209
|
+
lastWord = "";
|
|
210
|
+
i++;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const start = i;
|
|
214
|
+
i++;
|
|
215
|
+
let inClass = false;
|
|
216
|
+
while (i < len) {
|
|
217
|
+
const c = source[i];
|
|
218
|
+
if (c === "\\") {
|
|
219
|
+
i += 2;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (c === "\n") {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
if (c === "[") {
|
|
226
|
+
inClass = true;
|
|
227
|
+
} else if (c === "]") {
|
|
228
|
+
inClass = false;
|
|
229
|
+
} else if (c === "/" && !inClass) {
|
|
230
|
+
i++;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
ranges.push([start, Math.min(i, len)]);
|
|
236
|
+
lastCode = ")";
|
|
237
|
+
lastWord = "";
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (templateStack.length > 0) {
|
|
241
|
+
if (ch === "{") {
|
|
242
|
+
templateStack[templateStack.length - 1]++;
|
|
243
|
+
} else if (ch === "}") {
|
|
244
|
+
if (templateStack[templateStack.length - 1] === 0) {
|
|
245
|
+
templateStack.pop();
|
|
246
|
+
i = scanTemplateText(i);
|
|
247
|
+
lastCode = ")";
|
|
248
|
+
lastWord = "";
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
templateStack[templateStack.length - 1]--;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!/\s/.test(ch)) {
|
|
255
|
+
lastWord = WORD_CHAR.test(ch) ? (WORD_CHAR.test(lastCode) ? lastWord + ch : ch) : "";
|
|
256
|
+
lastCode = ch;
|
|
257
|
+
}
|
|
258
|
+
i++;
|
|
259
|
+
}
|
|
260
|
+
return ranges;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Whether `offset` falls inside any of the sorted protected `ranges`.
|
|
265
|
+
*
|
|
266
|
+
* @param {Array<[number, number]>} ranges - Output of {@link computeProtectedRanges}.
|
|
267
|
+
* @param {number} offset - Candidate match offset.
|
|
268
|
+
* @returns {boolean} True when the offset is protected.
|
|
269
|
+
*/
|
|
270
|
+
function isProtected(ranges, offset) {
|
|
271
|
+
for (const [start, end] of ranges) {
|
|
272
|
+
if (offset < start) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (offset < end) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The rspack loader entry point — see the file overview above for the full
|
|
284
|
+
* transform contract.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} source - The original module source text.
|
|
287
|
+
* @returns {string} The source text with `webpackIgnore` comments injected
|
|
288
|
+
* into every fully-dynamic `import(` call.
|
|
65
289
|
*/
|
|
66
290
|
module.exports = function webpackIgnoreDynamicImportsLoader(source) {
|
|
67
291
|
// Pass 1: string-literal and backtick arguments are excluded from this
|
|
68
292
|
// pass (the former never needs the comment; the latter is handled by
|
|
69
|
-
// pass 2 below)
|
|
70
|
-
//
|
|
293
|
+
// pass 2 below), as is an empty argument list — `import()` is prose, not
|
|
294
|
+
// a resolvable import. An optional single leading `/* ... */` magic
|
|
295
|
+
// comment is captured so it can be inspected: if it already contains
|
|
71
296
|
// `webpackIgnore`, the call is left untouched (idempotent); otherwise the
|
|
72
|
-
// new comment is prepended ahead of it.
|
|
73
|
-
|
|
297
|
+
// new comment is prepended ahead of it. Matches whose `import` keyword
|
|
298
|
+
// sits in a protected lexical region (comment/string/template-text/regex)
|
|
299
|
+
// are skipped.
|
|
300
|
+
let ranges = computeProtectedRanges(source);
|
|
301
|
+
let result = source.replace(/\bimport\s*\(\s*(\/\*[\s\S]*?\*\/\s*)?(?!["'`)])/g, (match, existingComment, offset) => {
|
|
302
|
+
if (isProtected(ranges, offset)) {
|
|
303
|
+
return match;
|
|
304
|
+
}
|
|
74
305
|
if (existingComment && /webpackIgnore/.test(existingComment)) {
|
|
75
306
|
return match;
|
|
76
307
|
}
|
|
@@ -79,8 +310,12 @@ module.exports = function webpackIgnoreDynamicImportsLoader(source) {
|
|
|
79
310
|
|
|
80
311
|
// Pass 2: backtick template-literal arguments. Only interpolated
|
|
81
312
|
// template literals (containing `${`) need the comment; a fully static
|
|
82
|
-
// one is left untouched.
|
|
313
|
+
// one is left untouched. Re-scan the pass-1 output — offsets shifted.
|
|
314
|
+
ranges = computeProtectedRanges(result);
|
|
83
315
|
result = result.replace(/\bimport\s*\(\s*`/g, (match, offset, full) => {
|
|
316
|
+
if (isProtected(ranges, offset)) {
|
|
317
|
+
return match;
|
|
318
|
+
}
|
|
84
319
|
const backtickIndex = offset + match.length - 1;
|
|
85
320
|
let end = backtickIndex + 1;
|
|
86
321
|
while (end < full.length && full[end] !== "`") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/github-action-builder",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A zero-config build tool for creating GitHub Actions from TypeScript. Bundles with rsbuild, validates action.yml against GitHub's schema, and outputs production-ready Node.js 24 actions.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@effect/platform-node": "4.0.0-beta.107",
|
|
52
|
-
"@effected/yaml": "^0.
|
|
52
|
+
"@effected/yaml": "^0.8.0",
|
|
53
53
|
"@rsbuild/core": "^2.1.9",
|
|
54
54
|
"effect": "4.0.0-beta.107",
|
|
55
55
|
"jiti": "^2.7.0",
|