@octanejs/cli 0.1.0 → 0.2.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/README.md +30 -6
- package/package.json +1 -1
- package/src/commands/analyze/fix.js +88 -0
- package/src/commands/analyze/index.js +291 -64
- package/src/commands/analyze/strong-coverage.js +21 -1
- package/src/commands/explain.js +73 -2
- package/src/data/octane-data.json +456 -109
|
@@ -115,6 +115,26 @@ export async function loadStrongPolicy(project, { required }) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* The installed compiler's leading `@jsxImportSource` scanner, for deciding
|
|
120
|
+
* which `.tsx` modules are Octane JSX when no Strong policy was loaded. `null`
|
|
121
|
+
* when the installed octane predates it.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} root
|
|
124
|
+
* @returns {Promise<Pick<StrongPolicy, 'jsxPragma'> | null>}
|
|
125
|
+
*/
|
|
126
|
+
export async function loadJsxPragma(root) {
|
|
127
|
+
try {
|
|
128
|
+
const require = createRequire(path.join(root, 'noop.js'));
|
|
129
|
+
const bundler = await import(pathToFileURL(require.resolve('octane/compiler/bundler')).href);
|
|
130
|
+
return typeof bundler.findLeadingJsxImportSourcePragma === 'function'
|
|
131
|
+
? { jsxPragma: bundler.findLeadingJsxImportSourcePragma }
|
|
132
|
+
: null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
/**
|
|
119
139
|
* An import of octane or one of its subpaths, capturing its clause. The clause
|
|
120
140
|
* may span lines but never reaches into the next `import`, so a semicolon-free
|
|
@@ -166,7 +186,7 @@ function isOctaneJsxSource(source) {
|
|
|
166
186
|
* @param {string} absolute
|
|
167
187
|
* @param {string} source
|
|
168
188
|
* @param {string | undefined} jsxImportSource from tsconfig
|
|
169
|
-
* @param {StrongPolicy} policy
|
|
189
|
+
* @param {Pick<StrongPolicy, 'jsxPragma'>} policy
|
|
170
190
|
*/
|
|
171
191
|
export function isOctaneModule(absolute, source, jsxImportSource, policy) {
|
|
172
192
|
if (absolute.endsWith('.d.ts')) return false;
|
package/src/commands/explain.js
CHANGED
|
@@ -28,6 +28,69 @@ export function parseErrorReference(input) {
|
|
|
28
28
|
return { code: code[1] ?? code[2], args };
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Find a Strong compiler diagnostic in what the user pasted: the code itself,
|
|
33
|
+
* the code without its `OCTANE_STRONG_` prefix, a whole compile error, or its
|
|
34
|
+
* documentation link.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} input
|
|
37
|
+
* @returns {string | null} the full code, whether or not it is known
|
|
38
|
+
*/
|
|
39
|
+
export function parseStrongReference(input) {
|
|
40
|
+
const code = /\bOCTANE_[A-Z0-9_]+\b/i.exec(input);
|
|
41
|
+
if (code) return code[0].toUpperCase();
|
|
42
|
+
const anchor = /#(octane-[a-z0-9-]+)/i.exec(input);
|
|
43
|
+
if (anchor) return anchor[1].toUpperCase().replaceAll('-', '_');
|
|
44
|
+
const short = /^\s*([A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+)\s*$/i.exec(input);
|
|
45
|
+
return short ? `OCTANE_STRONG_${short[1].toUpperCase()}` : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {import('../kernel/context.js').Ctx} ctx
|
|
50
|
+
* @param {string} code
|
|
51
|
+
*/
|
|
52
|
+
function explainStrong(ctx, code) {
|
|
53
|
+
const entry = DATA.strongDiagnostics?.diagnostics?.[code];
|
|
54
|
+
if (!entry) {
|
|
55
|
+
throw new CliError(`Unknown Strong diagnostic ${code}.`, {
|
|
56
|
+
hint: 'Upgrade @octanejs/cli if the code is newer, or see https://octanejs.dev/docs/strong-mode#diagnostic-reference',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** @typedef {{ id: string, title: string, react: string, strong: string, codes: string[] }} Recipe */
|
|
60
|
+
/** @type {Recipe[]} */
|
|
61
|
+
const recipes = (DATA.strongDiagnostics.recipes ?? []).filter((/** @type {Recipe} */ recipe) =>
|
|
62
|
+
recipe.codes.includes(code),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
ctx.ui.intro(code);
|
|
66
|
+
ctx.ui.log('');
|
|
67
|
+
ctx.ui.log(` ${entry.detects}`);
|
|
68
|
+
ctx.ui.log('');
|
|
69
|
+
ctx.ui.log(` ${ctx.ui.colors.bold('Replacement:')} ${entry.replacement}`);
|
|
70
|
+
for (const recipe of recipes) {
|
|
71
|
+
ctx.ui.log('');
|
|
72
|
+
ctx.ui.log(` ${ctx.ui.colors.bold(recipe.title)}`);
|
|
73
|
+
ctx.ui.log(` ${ctx.ui.colors.dim('React: ')} ${recipe.react}`);
|
|
74
|
+
ctx.ui.log(` ${ctx.ui.colors.dim('Strong:')} ${recipe.strong}`);
|
|
75
|
+
}
|
|
76
|
+
if (entry.severity === 'hint') {
|
|
77
|
+
ctx.ui.log('');
|
|
78
|
+
ctx.ui.log(` ${ctx.ui.colors.dim('severity: hint')}`);
|
|
79
|
+
}
|
|
80
|
+
ctx.ui.outro(entry.url);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
json: {
|
|
84
|
+
code,
|
|
85
|
+
severity: entry.severity,
|
|
86
|
+
detects: entry.detects,
|
|
87
|
+
replacement: entry.replacement,
|
|
88
|
+
recipes,
|
|
89
|
+
url: entry.url,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
31
94
|
/**
|
|
32
95
|
* @param {string} template
|
|
33
96
|
* @param {string[]} args
|
|
@@ -40,8 +103,9 @@ function fill(template, args) {
|
|
|
40
103
|
|
|
41
104
|
export default defineCommand({
|
|
42
105
|
description:
|
|
43
|
-
'Look up an Octane runtime error. Accepts a bare
|
|
44
|
-
'message a production build prints, arguments
|
|
106
|
+
'Look up an Octane runtime error or Strong compiler diagnostic. Accepts a bare\n' +
|
|
107
|
+
'code, the whole minified message a production build prints, arguments\n' +
|
|
108
|
+
'included, or a Strong code such as OCTANE_STRONG_RENDER_REF_READ.',
|
|
45
109
|
positionals: [
|
|
46
110
|
{ name: 'error', description: 'An error code, URL, or pasted message.', required: true },
|
|
47
111
|
],
|
|
@@ -50,6 +114,13 @@ export default defineCommand({
|
|
|
50
114
|
const raw = input.positionals.join(' ').trim();
|
|
51
115
|
if (!raw) throw usageError('Nothing to explain.', 'Try: octane explain 3');
|
|
52
116
|
|
|
117
|
+
// Runtime errors are numbered and Strong diagnostics are named, so a
|
|
118
|
+
// pasted message names at most one kind.
|
|
119
|
+
const strong = parseStrongReference(raw);
|
|
120
|
+
if (strong !== null && (/\bOCTANE_/i.test(raw) || parseErrorReference(raw) === null)) {
|
|
121
|
+
return explainStrong(ctx, strong);
|
|
122
|
+
}
|
|
123
|
+
|
|
53
124
|
const reference = parseErrorReference(raw);
|
|
54
125
|
if (!reference) {
|
|
55
126
|
throw usageError(`Could not find an error code in "${raw}".`, 'Try: octane explain 3');
|