@vzn/vx 0.0.139 → 0.0.141

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 CHANGED
@@ -52,7 +52,8 @@ npx vx run build --all # again: up-to-date in milliseconds
52
52
 
53
53
  Nothing is rewritten. `@vzn/vx-migrate` is not on npm yet (first publish
54
54
  pending); [`examples/turbo`](examples/turbo) runs this setup on every
55
- commit.
55
+ commit. Which `turbo.json`, `nx.json` and `project.json` keys vx honours,
56
+ key by key: [Turbo and Nx config support](https://vznjs.github.io/vx/compare/turbo-nx-support/).
56
57
 
57
58
  ## Why it is faster
58
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx",
3
- "version": "0.0.139",
3
+ "version": "0.0.141",
4
4
  "description": "A fast, correct task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "node": ">=18"
18
18
  },
19
19
  "optionalDependencies": {
20
- "@vzn/vx-linux-x64": "0.0.139",
21
- "@vzn/vx-linux-arm64": "0.0.139",
22
- "@vzn/vx-darwin-x64": "0.0.139",
23
- "@vzn/vx-darwin-arm64": "0.0.139"
20
+ "@vzn/vx-linux-x64": "0.0.141",
21
+ "@vzn/vx-linux-arm64": "0.0.141",
22
+ "@vzn/vx-darwin-x64": "0.0.141",
23
+ "@vzn/vx-darwin-arm64": "0.0.141"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -135,7 +135,12 @@ export class TaskLogBuffer {
135
135
  }
136
136
  if (acc.chars > TASK_LOG_TAIL_CHARS && acc.chunks.length === 1) {
137
137
  const only = acc.chunks[0]!
138
- const keep = only.slice(only.length - TASK_LOG_TAIL_CHARS)
138
+ let start = only.length - TASK_LOG_TAIL_CHARS
139
+ // A cut between a surrogate pair would open the tail on a lone low
140
+ // surrogate, which a sink's UTF-8 encode turns into U+FFFD.
141
+ const first = only.charCodeAt(start)
142
+ if (first >= 0xdc00 && first <= 0xdfff) start++
143
+ const keep = only.slice(start)
139
144
  acc.truncatedHeadChars += only.length - keep.length
140
145
  acc.chunks[0] = keep
141
146
  acc.chars = keep.length
@@ -8,7 +8,8 @@
8
8
  // <pattern>^... only the transitive deps of pattern (excluding the matched package)
9
9
  // ...^<pattern> only the transitive dependents of pattern (excluding the matched package)
10
10
  // !<pattern> exclude packages matching pattern from the selection
11
- // [<since>] projects affected since the given git ref
11
+ // [<since>] projects affected since the given git ref; after a name
12
+ // or {dir} selector, the selected ones only (D-44)
12
13
  // (Turbo-style; resolved upstream of applyFilters via
13
14
  // `affectedProjects` since it needs FS + git access)
14
15
  //
@@ -34,8 +35,9 @@ export interface ParsedFilter {
34
35
  matcher: string
35
36
  /**
36
37
  * When non-undefined, this filter is a git-relative `[<since>]`
37
- * selector. The matcher field is unused; the CLI resolves the ref
38
- * to a concrete set of project names before calling applyFilters.
38
+ * selector: bare (`matcher` empty), or narrowing a name or `{dir}`
39
+ * selector (`@scope/*[main]`, D-44). The CLI resolves the ref to a
40
+ * concrete set of project names before calling applyFilters.
39
41
  */
40
42
  gitSince?: string
41
43
  /** A path form carrying a glob (`./packages/*`): matched over the root-relative project dir. */
@@ -82,6 +84,18 @@ export function parseFilter(raw: string, workspaceRoot: string): ParsedFilter {
82
84
  }
83
85
  }
84
86
 
87
+ // `<selector>[<since>]`: the selected packages that changed since the
88
+ // ref (D-44). Turbo and pnpm read `@scope/*[HEAD]` and `{./apps/*}[main]`
89
+ // so; vx read the whole as one name glob and matched nothing.
90
+ let gitSince: string | undefined
91
+ // An unbraced path keeps its brackets: `./packages/[abc]` is a glob
92
+ // class, and Turbo takes a directory with a ref only as `{dir}[ref]`.
93
+ const scoped = /^(.+)\[([^\]]+)\]$/.exec(s)
94
+ if (scoped !== null && !scoped[1]!.startsWith('./') && scoped[1] !== '.') {
95
+ s = scoped[1]!
96
+ gitSince = scoped[2]!
97
+ }
98
+
85
99
  // `...`, `!`, `^...`: the operators with no project between them. An
86
100
  // empty name glob matched nothing and was hinted "Did you mean a?", and
87
101
  // an empty exclude (`!$UNSET`) excluded nothing, so every project ran
@@ -120,6 +134,7 @@ export function parseFilter(raw: string, workspaceRoot: string): ParsedFilter {
120
134
  isPath,
121
135
  matcher,
122
136
  ...(pathGlob !== undefined ? { pathGlob, pathRoot: workspaceRoot } : {}),
137
+ ...(gitSince !== undefined ? { gitSince } : {}),
123
138
  }
124
139
  }
125
140
 
@@ -132,8 +147,15 @@ function matchProjects(
132
147
  // is pure; git access happens upstream). Use the provided set as
133
148
  // the match set for this filter.
134
149
  if (filter.gitSince !== undefined) {
135
- return [...(affectedByFilter?.get(filter) ?? new Set())]
150
+ const changed = affectedByFilter?.get(filter) ?? new Set<string>()
151
+ if (filter.matcher === '') return [...changed]
152
+ return matchSelector(filter, projects).filter((name) => changed.has(name))
136
153
  }
154
+ return matchSelector(filter, projects)
155
+ }
156
+
157
+ /** The projects a name or path selector names, git ranges aside. */
158
+ function matchSelector(filter: ParsedFilter, projects: ProjectMeta[]): string[] {
137
159
  const out: string[] = []
138
160
  if (filter.isPath) {
139
161
  // A path naming a project's own directory is that project, as Turbo and