@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidvornholt/standards",
3
- "version": "0.22.2",
3
+ "version": "0.23.0",
4
4
  "description": "Bootstrap, synchronize, and validate davidvornholt/standards consumers",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -7,7 +7,7 @@ import {
7
7
  rootScriptExpectations,
8
8
  type StructureProfile,
9
9
  } from './structure-profile';
10
- import { hasSafeCommands, isSafeFilteredTurboAlias } from './structure-script';
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
- typeof script !== 'string' ||
151
- isSafeFilteredTurboAlias(script)
152
- ? []
153
- : [
154
- `package.json: root script "${name}" must delegate through Turbo with an explicit --filter`,
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 (
@@ -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
- export const isSafeFilteredTurboAlias = (script: string): boolean => {
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?.length !== 1) {
45
- return false;
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
- return tokens[0] !== 'turbo' || tokens[1] !== 'run'
57
- ? false
58
- : tokens[2]?.startsWith('-') === false &&
59
- !tokens.some((token) => NON_EXECUTING_TURBO_OPTION.test(token)) &&
60
- filterValue !== undefined &&
61
- filterValue !== '' &&
62
- !filterValue.startsWith('-');
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;