@davidvornholt/standards 0.22.2 → 0.23.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/package.json +1 -1
- package/src/structure-check.ts +8 -10
- package/src/structure-script.ts +71 -10
package/package.json
CHANGED
package/src/structure-check.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
rootScriptExpectations,
|
|
8
8
|
type StructureProfile,
|
|
9
9
|
} from './structure-profile';
|
|
10
|
-
import {
|
|
10
|
+
import { filteredTurboAliasProblem, hasSafeCommands } from './structure-script';
|
|
11
11
|
import { inspectWorkspace, type Workspace } from './structure-workspace';
|
|
12
12
|
|
|
13
13
|
const ROOT_FIXED_SCRIPTS = new Set(
|
|
@@ -145,15 +145,13 @@ const inspectRootScripts = (
|
|
|
145
145
|
`package.json: root script "${name}" must run ${exact ? 'exactly ' : ''}${requirement}`,
|
|
146
146
|
];
|
|
147
147
|
});
|
|
148
|
-
const aliasProblems = Object.entries(scripts).flatMap(([name, script]) =>
|
|
149
|
-
ROOT_FIXED_SCRIPTS.has(name) ||
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
],
|
|
156
|
-
);
|
|
148
|
+
const aliasProblems = Object.entries(scripts).flatMap(([name, script]) => {
|
|
149
|
+
if (ROOT_FIXED_SCRIPTS.has(name) || typeof script !== 'string') {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const problem = filteredTurboAliasProblem(name, script);
|
|
153
|
+
return problem === null ? [] : [problem];
|
|
154
|
+
});
|
|
157
155
|
return [...gateProblems, ...aliasProblems];
|
|
158
156
|
};
|
|
159
157
|
export const collectStructureProblems = async (
|
package/src/structure-script.ts
CHANGED
|
@@ -2,6 +2,18 @@ const UNSAFE_SCRIPT_SYNTAX = /[|;#"'`\r\n]/u;
|
|
|
2
2
|
const SCRIPT_WHITESPACE = /\s+/u;
|
|
3
3
|
const NON_EXECUTING_TURBO_OPTION = /^(?:-h|-v|--(?:dry|help|version))(?:=|$)/u;
|
|
4
4
|
|
|
5
|
+
type FilteredTurboAliasViolation =
|
|
6
|
+
| 'empty'
|
|
7
|
+
| 'filter-empty'
|
|
8
|
+
| 'filter-missing'
|
|
9
|
+
| 'filter-option'
|
|
10
|
+
| 'multiple-commands'
|
|
11
|
+
| 'non-executing-option'
|
|
12
|
+
| 'not-run'
|
|
13
|
+
| 'not-turbo'
|
|
14
|
+
| 'syntax'
|
|
15
|
+
| 'task';
|
|
16
|
+
|
|
5
17
|
export const parseSafeCommands = (
|
|
6
18
|
script: string | null,
|
|
7
19
|
): ReadonlyArray<ReadonlyArray<string>> | null => {
|
|
@@ -39,12 +51,32 @@ export const hasSafeCommand = (
|
|
|
39
51
|
expected: string,
|
|
40
52
|
): boolean => hasSafeCommands(script, [expected], false);
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
const filteredTurboAliasViolation = (
|
|
55
|
+
script: string,
|
|
56
|
+
): FilteredTurboAliasViolation | null => {
|
|
57
|
+
if (script.trim() === '') {
|
|
58
|
+
return 'empty';
|
|
59
|
+
}
|
|
43
60
|
const commands = parseSafeCommands(script);
|
|
44
|
-
if (commands
|
|
45
|
-
return
|
|
61
|
+
if (commands === null) {
|
|
62
|
+
return 'syntax';
|
|
63
|
+
}
|
|
64
|
+
if (commands.length !== 1) {
|
|
65
|
+
return 'multiple-commands';
|
|
46
66
|
}
|
|
47
67
|
const [tokens] = commands;
|
|
68
|
+
if (tokens[0] !== 'turbo') {
|
|
69
|
+
return 'not-turbo';
|
|
70
|
+
}
|
|
71
|
+
if (tokens[1] !== 'run') {
|
|
72
|
+
return 'not-run';
|
|
73
|
+
}
|
|
74
|
+
if (tokens.some((token) => NON_EXECUTING_TURBO_OPTION.test(token))) {
|
|
75
|
+
return 'non-executing-option';
|
|
76
|
+
}
|
|
77
|
+
if (tokens[2]?.startsWith('-') !== false) {
|
|
78
|
+
return 'task';
|
|
79
|
+
}
|
|
48
80
|
const filterAt = tokens.findIndex(
|
|
49
81
|
(token) => token === '--filter' || token.startsWith('--filter='),
|
|
50
82
|
);
|
|
@@ -53,11 +85,40 @@ export const isSafeFilteredTurboAlias = (script: string): boolean => {
|
|
|
53
85
|
filter === '--filter'
|
|
54
86
|
? tokens[filterAt + 1]
|
|
55
87
|
: filter?.slice('--filter='.length);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
if (filter === undefined) {
|
|
89
|
+
return 'filter-missing';
|
|
90
|
+
}
|
|
91
|
+
if (filterValue === undefined || filterValue === '') {
|
|
92
|
+
return 'filter-empty';
|
|
93
|
+
}
|
|
94
|
+
return filterValue.startsWith('-') ? 'filter-option' : null;
|
|
63
95
|
};
|
|
96
|
+
|
|
97
|
+
export const filteredTurboAliasProblem = (
|
|
98
|
+
name: string,
|
|
99
|
+
script: string,
|
|
100
|
+
): string | null => {
|
|
101
|
+
const violation = filteredTurboAliasViolation(script);
|
|
102
|
+
if (violation === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const details: Record<FilteredTurboAliasViolation, string> = {
|
|
106
|
+
empty: 'must be a non-empty single filtered Turbo command',
|
|
107
|
+
'filter-empty': 'must pass a non-empty value to --filter',
|
|
108
|
+
'filter-missing': 'must pass an explicit --filter',
|
|
109
|
+
'filter-option': 'must pass a --filter value that is not another option',
|
|
110
|
+
'multiple-commands':
|
|
111
|
+
'must contain exactly one command; && command chains are not supported',
|
|
112
|
+
'non-executing-option':
|
|
113
|
+
'must execute a task; Turbo help, version, and dry-run options are not allowed',
|
|
114
|
+
'not-run': 'must invoke Turbo through "turbo run"',
|
|
115
|
+
'not-turbo': 'must invoke Turbo directly with "turbo"',
|
|
116
|
+
syntax:
|
|
117
|
+
'contains shell syntax the structure gate does not parse (quotes, |, ;, #, backticks, $(, CR/LF line breaks, or malformed ampersand separators); write one command with plain arguments, using --filter=./apps/* for a glob',
|
|
118
|
+
task: 'must put a task name immediately after "turbo run"',
|
|
119
|
+
};
|
|
120
|
+
return `package.json: root script "${name}" ${details[violation]}`;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const isSafeFilteredTurboAlias = (script: string): boolean =>
|
|
124
|
+
filteredTurboAliasViolation(script) === null;
|