@tailor-platform/sdk-codemod 0.3.0-next.0 → 0.3.0-next.1

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/CHANGELOG.md CHANGED
@@ -1,10 +1,31 @@
1
1
  # @tailor-platform/sdk-codemod
2
2
 
3
+ ## 0.3.0-next.1
4
+ ### Patch Changes
5
+
6
+
7
+
8
+ - [#1460](https://github.com/tailor-platform/sdk/pull/1460) [`f49c6d1`](https://github.com/tailor-platform/sdk/commit/f49c6d1b5a856969cb4e04ae7d3a87ed34aa020f) Thanks [@dqn](https://github.com/dqn)! - Remove the v1 runtime globals compatibility layer. Importing from `@tailor-platform/sdk` no longer activates the ambient `tailor.*` / `tailordb.*` declarations; opt into globals with `@tailor-platform/sdk/runtime/globals` or use the typed wrappers from `@tailor-platform/sdk/runtime`.
9
+
10
+ The capital-cased `Tailordb.*` namespace is removed. If your project still references `Tailordb.QueryResult`, `Tailordb.CommandType`, `Tailordb.Client`, or `typeof Tailordb.Client`, migrate before upgrading: run `pnpm dlx @tailor-platform/sdk-codemod v2/tailordb-namespace` to rewrite them to lowercase `tailordb.*`, then add `import "@tailor-platform/sdk/runtime/globals"` so the rewritten references resolve.
11
+
12
+
13
+ - [#1457](https://github.com/tailor-platform/sdk/pull/1457) [`84325f8`](https://github.com/tailor-platform/sdk/commit/84325f8602a5631b7c323c997b1425235509920e) Thanks [@dqn](https://github.com/dqn)! - Remove deprecated CLI aliases for the v2 command surface. Use `tailor-sdk deploy` instead of `tailor-sdk apply`, `tailor-sdk crashreport` instead of `tailor-sdk crash-report`, and the hyphenated `--machine-user` option instead of the hidden `--machineuser` alias.
14
+
15
+ Fix the v2 CLI rename codemod to migrate the hidden `--machineuser` option to `--machine-user`.
16
+
3
17
  ## 0.3.0-next.0
4
18
  ### Minor Changes
5
19
 
6
20
 
7
21
 
22
+ - [#1435](https://github.com/tailor-platform/sdk/pull/1435) [`49c0cc9`](https://github.com/tailor-platform/sdk/commit/49c0cc99171d7e317a50a18804a21067d89f9493) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/plugin-cli-import` codemod so `tailor-sdk upgrade` rewrites deprecated plugin imports from `@tailor-platform/sdk/cli` (`kyselyTypePlugin`, `enumConstantsPlugin`, `fileUtilsPlugin`, `seedPlugin`) to their dedicated `@tailor-platform/sdk/plugin/*` subpaths, splitting any non-plugin specifiers onto a separate import.
23
+
24
+ ## 0.3.0
25
+ ### Minor Changes
26
+
27
+
28
+
8
29
  - [#1435](https://github.com/tailor-platform/sdk/pull/1435) [`49c0cc9`](https://github.com/tailor-platform/sdk/commit/49c0cc99171d7e317a50a18804a21067d89f9493) Thanks [@dqn](https://github.com/dqn)! - Add the `v2/plugin-cli-import` codemod so `tailor-sdk upgrade` rewrites deprecated plugin imports from `@tailor-platform/sdk/cli` (`kyselyTypePlugin`, `enumConstantsPlugin`, `fileUtilsPlugin`, `seedPlugin`) to their dedicated `@tailor-platform/sdk/plugin/*` subpaths, splitting any non-plugin specifiers onto a separate import.
9
30
 
10
31
  ## 0.2.7
@@ -1,8 +1,10 @@
1
1
  import * as path from "pathe";
2
2
  //#region codemods/v2/apply-to-deploy/scripts/transform.ts
3
- const APPLY_PATTERN = /\btailor-sdk(@[^\s'"`]+)?(\s+)apply(?![-\w])/g;
3
+ const ARG_VALUE = `(?:[^\\s'"\`;&|]+|'[^']*'|"(?:(?:\\\\.)|[^"\\\\])*")`;
4
+ const GLOBAL_ARG_PATTERN = `(?:(?:\\s+(?:--verbose|--json|-j))|(?:\\s+(?:--env-file|--env-file-if-exists|-e)(?:=${ARG_VALUE}|\\s+${ARG_VALUE})))*`;
5
+ const APPLY_PATTERN = new RegExp(`(?<![\\w-])tailor-sdk(?:@[^\\s'"\`]+)?(?![\\w-])(${GLOBAL_ARG_PATTERN}\\s+)apply(?![-\\w])`, "g");
4
6
  function replaceApply(value) {
5
- return value.replace(APPLY_PATTERN, (_match, ver, sep) => `tailor-sdk${ver ?? ""}${sep}deploy`);
7
+ return value.replace(APPLY_PATTERN, (match) => `${match.slice(0, -5)}deploy`);
6
8
  }
7
9
  function transformText(source) {
8
10
  if (!APPLY_PATTERN.test(source)) return null;
@@ -35,7 +37,7 @@ function transformPackageJson(source) {
35
37
  /**
36
38
  * Replace `tailor-sdk apply` invocations with `tailor-sdk deploy`.
37
39
  *
38
- * `deploy` is a v1 alias of `apply` and the recommended name going forward.
40
+ * `deploy` is the canonical v2 command name.
39
41
  * @param source - File contents
40
42
  * @param filePath - Absolute path to the file (used to dispatch package.json vs text)
41
43
  * @returns Transformed source or null when nothing matched.
@@ -1,15 +1,377 @@
1
1
  import * as path from "pathe";
2
2
  //#region codemods/v2/cli-rename/scripts/transform.ts
3
3
  const COMMAND_RENAMES = [["crash-report", "crashreport"]];
4
- const COMMAND_PATTERN = new RegExp(`\\btailor-sdk(@[^\\s'"\`]+)?(\\s+)(${COMMAND_RENAMES.map(([from]) => from).join("|")})\\b`, "g");
4
+ const OPTION_RENAMES = [["--machineuser", "--machine-user"]];
5
+ const ARG_VALUE = `(?:[^\\s'"\`;&|]+|'[^']*'|"(?:(?:\\\\.)|[^"\\\\])*")`;
6
+ const GLOBAL_ARG_PATTERN = `(?:(?:\\s+(?:--verbose|--json|-j))|(?:\\s+(?:--env-file|--env-file-if-exists|-e)(?:=${ARG_VALUE}|\\s+${ARG_VALUE})))*`;
7
+ const TAILOR_BINARY = `(?<![\\w-])tailor-sdk(?:@[^\\s'"\`]+)?(?![\\w-])`;
8
+ const COMMAND_PATTERN = new RegExp(`${TAILOR_BINARY}(${GLOBAL_ARG_PATTERN}\\s+)(${COMMAND_RENAMES.map(([from]) => from).join("|")})\\b`, "g");
9
+ const TAILOR_BINARY_PATTERN = new RegExp(TAILOR_BINARY, "g");
5
10
  const COMMAND_MAP = new Map(COMMAND_RENAMES);
6
- function replaceAll(value) {
7
- return value.replace(COMMAND_PATTERN, (_match, ver, sep, cmd) => `tailor-sdk${ver ?? ""}${sep}${COMMAND_MAP.get(cmd) ?? cmd}`);
11
+ function isOptionBoundaryChar(value) {
12
+ return value === void 0 || !/[\w-]/.test(value);
8
13
  }
9
- function transformText(source) {
10
- if (!COMMAND_PATTERN.test(source)) return null;
11
- COMMAND_PATTERN.lastIndex = 0;
12
- const updated = replaceAll(source);
14
+ function findInlineCodeSpanEnd(source, start) {
15
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
16
+ if ([...source.slice(lineStart, start).matchAll(/`/g)].length % 2 === 0) return void 0;
17
+ const codeSpanEnd = source.indexOf("`", start);
18
+ return codeSpanEnd === -1 ? void 0 : codeSpanEnd;
19
+ }
20
+ function findEnclosingLineQuoteEnd(source, start) {
21
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
22
+ const lineEnd = source.indexOf("\n", start);
23
+ const limit = lineEnd === -1 ? source.length : lineEnd;
24
+ let quote = null;
25
+ for (let index = lineStart; index < start; index += 1) {
26
+ const ch = source[index];
27
+ if (quote !== null) {
28
+ if (ch === "\\" && quote === "\"" && index + 1 < start) {
29
+ index += 1;
30
+ continue;
31
+ }
32
+ if (ch === quote) quote = null;
33
+ continue;
34
+ }
35
+ if (ch === "'" || ch === "\"") quote = ch;
36
+ }
37
+ if (quote === null) return void 0;
38
+ for (let index = start; index < limit; index += 1) {
39
+ const ch = source[index];
40
+ if (ch === "\\" && quote === "\"" && index + 1 < limit) {
41
+ index += 1;
42
+ continue;
43
+ }
44
+ if (ch === quote) return index;
45
+ }
46
+ }
47
+ function lineIndent(line) {
48
+ return line.match(/^ */)?.[0].length ?? 0;
49
+ }
50
+ function isFoldedScalarHeader(line) {
51
+ return /^\s*(?:-\s*)?[^#\n]*:\s*>[+-]?(?:\s*(?:#.*)?)?$/.test(line);
52
+ }
53
+ function findFoldedYamlRanges(source) {
54
+ const ranges = [];
55
+ const lines = source.match(/^.*(?:\n|$)/gm) ?? [];
56
+ let offset = 0;
57
+ for (let index = 0; index < lines.length; index += 1) {
58
+ const line = lines[index];
59
+ const body = line.replace(/\r?\n$/, "");
60
+ offset += line.length;
61
+ if (!isFoldedScalarHeader(body)) continue;
62
+ const baseIndent = lineIndent(body);
63
+ let rangeStart;
64
+ let rangeEnd;
65
+ let cursor = offset;
66
+ for (let nextIndex = index + 1; nextIndex < lines.length; nextIndex += 1) {
67
+ const nextLine = lines[nextIndex];
68
+ const nextBody = nextLine.replace(/\r?\n$/, "");
69
+ const trimmed = nextBody.trim();
70
+ const indent = lineIndent(nextBody);
71
+ if (trimmed !== "" && indent <= baseIndent) break;
72
+ if (trimmed === "") {
73
+ if (rangeStart !== void 0 && rangeEnd !== void 0) {
74
+ ranges.push({
75
+ start: rangeStart,
76
+ end: rangeEnd
77
+ });
78
+ rangeStart = void 0;
79
+ rangeEnd = void 0;
80
+ }
81
+ } else {
82
+ rangeStart ??= cursor;
83
+ rangeEnd = cursor + nextLine.length;
84
+ }
85
+ cursor += nextLine.length;
86
+ }
87
+ if (rangeStart !== void 0 && rangeEnd !== void 0) ranges.push({
88
+ start: rangeStart,
89
+ end: rangeEnd
90
+ });
91
+ }
92
+ return ranges;
93
+ }
94
+ function findMarkdownFencedCodeRanges(source) {
95
+ const ranges = [];
96
+ const lines = source.match(/^.*(?:\n|$)/gm) ?? [];
97
+ let offset = 0;
98
+ let open;
99
+ for (const line of lines) {
100
+ const body = line.replace(/\r?\n$/, "");
101
+ if (open) {
102
+ const marker = body.match(/^ {0,3}(`{3,}|~{3,})\s*$/)?.[1];
103
+ if (marker && marker[0] === open.char && marker.length >= open.length) {
104
+ ranges.push({
105
+ start: open.start,
106
+ end: offset
107
+ });
108
+ open = void 0;
109
+ }
110
+ } else {
111
+ const marker = body.match(/^ {0,3}(`{3,}|~{3,}).*$/)?.[1];
112
+ if (marker) open = {
113
+ char: marker[0],
114
+ length: marker.length,
115
+ start: offset + line.length
116
+ };
117
+ }
118
+ offset += line.length;
119
+ }
120
+ if (open) ranges.push({
121
+ start: open.start,
122
+ end: source.length
123
+ });
124
+ return ranges;
125
+ }
126
+ function isCommandSeparator(source, index) {
127
+ const ch = source[index];
128
+ const prev = source[index - 1];
129
+ if (prev === "\\") return false;
130
+ if (ch === "&") {
131
+ const next = source[index + 1];
132
+ if (prev === ">" || prev === "<" || next === ">") return false;
133
+ }
134
+ return ch === ";" || ch === "&" || ch === "|";
135
+ }
136
+ function startsCommandSubstitution(source, index) {
137
+ return source[index] === "$" && source[index + 1] === "(" && source[index - 1] !== "\\";
138
+ }
139
+ function findCommandSubstitutionEnd(source, start) {
140
+ let depth = 1;
141
+ let index = start + 2;
142
+ let quote = null;
143
+ while (index < source.length) {
144
+ const ch = source[index];
145
+ if (quote !== null) {
146
+ if (quote.escaped) {
147
+ if (ch === "\\" && source[index + 1] === quote.char) {
148
+ quote = null;
149
+ index += 2;
150
+ continue;
151
+ }
152
+ index += 1;
153
+ continue;
154
+ }
155
+ if (quote.char === "\"" && startsCommandSubstitution(source, index)) {
156
+ depth += 1;
157
+ index += 2;
158
+ continue;
159
+ }
160
+ if (ch === "\\" && quote.char === "\"" && index + 1 < source.length) {
161
+ index += 2;
162
+ continue;
163
+ }
164
+ if (ch === quote.char) quote = null;
165
+ index += 1;
166
+ continue;
167
+ }
168
+ if (ch === "\\" && source[index + 1] === "\"") {
169
+ quote = {
170
+ char: "\"",
171
+ escaped: true
172
+ };
173
+ index += 2;
174
+ continue;
175
+ }
176
+ if (ch === "'" || ch === "\"") {
177
+ quote = {
178
+ char: ch,
179
+ escaped: false
180
+ };
181
+ index += 1;
182
+ continue;
183
+ }
184
+ if (startsCommandSubstitution(source, index)) {
185
+ depth += 1;
186
+ index += 2;
187
+ continue;
188
+ }
189
+ if (ch === ")") {
190
+ depth -= 1;
191
+ if (depth === 0) return index;
192
+ }
193
+ index += 1;
194
+ }
195
+ }
196
+ function findContainingRange(ranges, index) {
197
+ return ranges?.find((range) => range.start <= index && index < range.end);
198
+ }
199
+ function findTailorCommandEnd(source, start, foldedYamlRanges, markdownFencedCodeRanges) {
200
+ const inlineCodeSpanEnd = findInlineCodeSpanEnd(source, start);
201
+ const enclosingLineQuoteEnd = findEnclosingLineQuoteEnd(source, start);
202
+ const limit = Math.min(inlineCodeSpanEnd ?? source.length, enclosingLineQuoteEnd ?? source.length);
203
+ const foldedYamlRange = findContainingRange(foldedYamlRanges, start);
204
+ const markdownFencedCodeRange = findContainingRange(markdownFencedCodeRanges, start);
205
+ const delimitedRange = foldedYamlRange ?? markdownFencedCodeRange;
206
+ const commandLimit = delimitedRange ? Math.min(limit, delimitedRange.end) : limit;
207
+ let quote = null;
208
+ let end = start;
209
+ while (end < commandLimit) {
210
+ const ch = source[end];
211
+ if (quote !== null) {
212
+ if (quote.escaped) {
213
+ if (ch === "\\" && source[end + 1] === quote.char) {
214
+ quote = null;
215
+ end += 2;
216
+ continue;
217
+ }
218
+ end += 1;
219
+ continue;
220
+ }
221
+ if (quote.char === "\"" && startsCommandSubstitution(source, end)) {
222
+ const substitutionEnd = findCommandSubstitutionEnd(source, end);
223
+ if (substitutionEnd !== void 0) {
224
+ end = substitutionEnd + 1;
225
+ continue;
226
+ }
227
+ }
228
+ if (ch === "\\" && quote.char === "\"" && end + 1 < commandLimit) {
229
+ end += 2;
230
+ continue;
231
+ }
232
+ if (ch === quote.char) quote = null;
233
+ end += 1;
234
+ continue;
235
+ }
236
+ if (ch === "\\" && source[end + 1] === "\"") {
237
+ quote = {
238
+ char: "\"",
239
+ escaped: true
240
+ };
241
+ end += 2;
242
+ continue;
243
+ }
244
+ if (ch === "'" || ch === "\"") {
245
+ quote = {
246
+ char: ch,
247
+ escaped: false
248
+ };
249
+ end += 1;
250
+ continue;
251
+ }
252
+ if (startsCommandSubstitution(source, end)) {
253
+ const substitutionEnd = findCommandSubstitutionEnd(source, end);
254
+ if (substitutionEnd !== void 0) {
255
+ end = substitutionEnd + 1;
256
+ continue;
257
+ }
258
+ }
259
+ const prev = source[end - 1];
260
+ if (ch === ")") break;
261
+ if (isCommandSeparator(source, end)) break;
262
+ if (ch === "\n" && prev !== "\\" && !foldedYamlRange) break;
263
+ end += 1;
264
+ }
265
+ return end;
266
+ }
267
+ function isDelimitedCommandContext(source, start, foldedYamlRanges, markdownFencedCodeRanges) {
268
+ return findInlineCodeSpanEnd(source, start) !== void 0 || findEnclosingLineQuoteEnd(source, start) !== void 0 || findContainingRange(foldedYamlRanges, start) !== void 0 || findContainingRange(markdownFencedCodeRanges, start) !== void 0;
269
+ }
270
+ function findOptionRename(command, index) {
271
+ return OPTION_RENAMES.find(([from]) => command.startsWith(from, index) && isOptionBoundaryChar(command[index - 1]) && isOptionBoundaryChar(command[index + from.length]));
272
+ }
273
+ function replaceOptionsInCommand(command) {
274
+ let updated = "";
275
+ let index = 0;
276
+ let quote = null;
277
+ while (index < command.length) {
278
+ const ch = command[index];
279
+ if (quote !== null) {
280
+ if (quote.char === "\"" && startsCommandSubstitution(command, index)) {
281
+ const substitutionEnd = findCommandSubstitutionEnd(command, index);
282
+ if (substitutionEnd !== void 0) {
283
+ updated += "$(";
284
+ updated += replaceAll(command.slice(index + 2, substitutionEnd));
285
+ updated += ")";
286
+ index = substitutionEnd + 1;
287
+ continue;
288
+ }
289
+ }
290
+ updated += ch;
291
+ if (quote.escaped) {
292
+ if (ch === "\\" && command[index + 1] === quote.char) {
293
+ index += 1;
294
+ updated += command[index];
295
+ quote = null;
296
+ }
297
+ } else if (ch === "\\" && quote.char === "\"" && index + 1 < command.length) {
298
+ index += 1;
299
+ updated += command[index];
300
+ } else if (ch === quote.char) quote = null;
301
+ index += 1;
302
+ continue;
303
+ }
304
+ if (ch === "\\" && command[index + 1] === "\"") {
305
+ quote = {
306
+ char: "\"",
307
+ escaped: true
308
+ };
309
+ updated += ch;
310
+ index += 1;
311
+ updated += command[index];
312
+ index += 1;
313
+ continue;
314
+ }
315
+ if (ch === "'" || ch === "\"") {
316
+ quote = {
317
+ char: ch,
318
+ escaped: false
319
+ };
320
+ updated += ch;
321
+ index += 1;
322
+ continue;
323
+ }
324
+ if (startsCommandSubstitution(command, index)) {
325
+ const substitutionEnd = findCommandSubstitutionEnd(command, index);
326
+ if (substitutionEnd !== void 0) {
327
+ updated += "$(";
328
+ updated += replaceAll(command.slice(index + 2, substitutionEnd));
329
+ updated += ")";
330
+ index = substitutionEnd + 1;
331
+ continue;
332
+ }
333
+ }
334
+ const rename = findOptionRename(command, index);
335
+ if (rename) {
336
+ updated += rename[1];
337
+ index += rename[0].length;
338
+ continue;
339
+ }
340
+ updated += ch;
341
+ index += 1;
342
+ }
343
+ return updated;
344
+ }
345
+ function replaceOptionsInTailorCommands(source, foldedYamlRanges, requireDelimitedContext = false, markdownFencedCodeRanges) {
346
+ let updated = "";
347
+ let cursor = 0;
348
+ TAILOR_BINARY_PATTERN.lastIndex = 0;
349
+ for (;;) {
350
+ const match = TAILOR_BINARY_PATTERN.exec(source);
351
+ if (!match) break;
352
+ const start = match.index;
353
+ if (start < cursor) continue;
354
+ if (requireDelimitedContext && !isDelimitedCommandContext(source, start, foldedYamlRanges, markdownFencedCodeRanges)) {
355
+ TAILOR_BINARY_PATTERN.lastIndex = start + match[0].length;
356
+ continue;
357
+ }
358
+ const end = findTailorCommandEnd(source, start, foldedYamlRanges, markdownFencedCodeRanges);
359
+ updated += source.slice(cursor, start);
360
+ updated += replaceOptionsInCommand(source.slice(start, end));
361
+ cursor = end;
362
+ TAILOR_BINARY_PATTERN.lastIndex = end;
363
+ }
364
+ return updated + source.slice(cursor);
365
+ }
366
+ function replaceAll(value, parseFoldedYaml = false, requireDelimitedContext = false, parseMarkdownFencedCode = false) {
367
+ const updated = value.replace(COMMAND_PATTERN, (match, _prefix, cmd) => `${match.slice(0, -cmd.length)}${COMMAND_MAP.get(cmd) ?? cmd}`);
368
+ return replaceOptionsInTailorCommands(updated, parseFoldedYaml ? findFoldedYamlRanges(updated) : void 0, requireDelimitedContext, parseMarkdownFencedCode ? findMarkdownFencedCodeRanges(updated) : void 0);
369
+ }
370
+ function transformText(source, filePath) {
371
+ const ext = path.extname(filePath).toLowerCase();
372
+ const isYaml = ext === ".yml" || ext === ".yaml";
373
+ const isMarkdown = ext === ".md";
374
+ const updated = replaceAll(source, isYaml, isMarkdown, isMarkdown);
13
375
  return updated === source ? null : updated;
14
376
  }
15
377
  function transformPackageJson(source) {
@@ -35,20 +397,17 @@ function transformPackageJson(source) {
35
397
  }
36
398
  /**
37
399
  * Apply v2 CLI naming conventions: multi-word commands collapse into a single
38
- * word (`crash-report` → `crashreport`). Optional `@version` pins on the binary
39
- * (`tailor-sdk@latest`) are preserved.
400
+ * word (`crash-report` → `crashreport`), and legacy option spellings are
401
+ * rewritten to kebab-case (`--machineuser` → `--machine-user`). Optional
402
+ * `@version` pins on the binary (`tailor-sdk@latest`) are preserved.
40
403
  *
41
- * Long options (`--executionId`, `--executorName`, `--jobId`) and the
42
- * positional argument keys with the same names are intentionally not rewritten:
43
- * those tokens are positional in the SDK CLI and never appear as long flags in
44
- * user scripts, so a transform here would have no real-world target.
45
404
  * @param source - File contents
46
405
  * @param filePath - Absolute path to the file (used to dispatch package.json vs text)
47
406
  * @returns Transformed source or null when nothing matched.
48
407
  */
49
408
  function transform(source, filePath) {
50
409
  if (path.extname(filePath).toLowerCase() === ".json") return transformPackageJson(source);
51
- return transformText(source);
410
+ return transformText(source, filePath);
52
411
  }
53
412
  //#endregion
54
413
  export { transform as default };
@@ -6,10 +6,11 @@ const MEMBER_GROUP = [
6
6
  ].join("|");
7
7
  const PATTERN = new RegExp(String.raw`\bTailordb\.(${MEMBER_GROUP})\b`, "g");
8
8
  /**
9
- * Rewrite references to the deprecated capital-cased `Tailordb` ambient
10
- * namespace to the new lowercase `tailordb` namespace. The capital-cased
11
- * namespace was inherited from `@tailor-platform/function-types`; the SDK
12
- * keeps it as a `@deprecated` alias in v1 and removes it in v2.
9
+ * Rewrite references to the capital-cased `Tailordb` ambient namespace to the
10
+ * lowercase `tailordb` namespace. The capital-cased namespace was inherited
11
+ * from `@tailor-platform/function-types`; the SDK kept it as a `@deprecated`
12
+ * alias in v1 and removed it in v2, leaving only the lowercase `tailordb.*`
13
+ * namespace exposed by `@tailor-platform/sdk/runtime/globals`.
13
14
  *
14
15
  * Only the known type-only members (`QueryResult`, `CommandType`, `Client`)
15
16
  * are rewritten so that unrelated user-defined symbols sharing the
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ import { arg, defineCommand, runMain } from "politty";
7
7
  import { z } from "zod";
8
8
  import { gte, lt, valid } from "semver";
9
9
  import * as fs from "node:fs";
10
- import { glob } from "node:fs/promises";
11
10
  import chalk from "chalk";
12
11
  import { structuredPatch } from "diff";
13
12
  import picomatch from "picomatch";
@@ -75,7 +74,7 @@ const allCodemods = [
75
74
  {
76
75
  id: "v2/apply-to-deploy",
77
76
  name: "tailor-sdk apply → tailor-sdk deploy",
78
- description: "Rewrite `tailor-sdk apply` invocations in package.json scripts, shell scripts, CI configs, and docs to the v2-recommended `tailor-sdk deploy` alias",
77
+ description: "Rewrite `tailor-sdk apply` invocations in package.json scripts, shell scripts, CI configs, and docs to the canonical v2 `tailor-sdk deploy` command",
79
78
  since: "1.0.0",
80
79
  until: "2.0.0",
81
80
  scriptPath: "v2/apply-to-deploy/scripts/transform.js",
@@ -87,8 +86,8 @@ const allCodemods = [
87
86
  },
88
87
  {
89
88
  id: "v2/cli-rename",
90
- name: "v2 CLI rename (single-word commands)",
91
- description: "Rewrite `tailor-sdk crash-report` invocations to the v2 single-word `tailor-sdk crashreport` form across package.json scripts, shell scripts, CI configs, and docs",
89
+ name: "v2 CLI rename",
90
+ description: "Rewrite `tailor-sdk crash-report` to `tailor-sdk crashreport` and `--machineuser` to `--machine-user` across package.json scripts, shell scripts, CI configs, and docs",
92
91
  since: "1.0.0",
93
92
  until: "2.0.0",
94
93
  scriptPath: "v2/cli-rename/scripts/transform.js",
@@ -96,7 +95,8 @@ const allCodemods = [
96
95
  "**/package.json",
97
96
  "**/*.{sh,bash,zsh,yml,yaml}",
98
97
  "**/*.md"
99
- ]
98
+ ],
99
+ legacyPatterns: ["tailor-sdk crash-report", "--machineuser"]
100
100
  },
101
101
  {
102
102
  id: "v2/auth-invoker-unwrap",
@@ -110,7 +110,7 @@ const allCodemods = [
110
110
  {
111
111
  id: "v2/tailordb-namespace",
112
112
  name: "Tailordb → tailordb (lowercase ambient namespace)",
113
- description: "Rewrite references to the deprecated capital-cased `Tailordb` ambient namespace (`Tailordb.QueryResult`, `Tailordb.CommandType`, `Tailordb.Client`, `typeof Tailordb.Client`) to the new lowercase `tailordb.*` namespace re-published by the SDK in place of `@tailor-platform/function-types`.",
113
+ description: "Rewrite references to the removed capital-cased `Tailordb` ambient namespace (`Tailordb.QueryResult`, `Tailordb.CommandType`, `Tailordb.Client`, `typeof Tailordb.Client`) to the lowercase `tailordb.*` namespace exposed by `@tailor-platform/sdk/runtime/globals`.",
114
114
  since: "1.0.0",
115
115
  until: "2.0.0",
116
116
  scriptPath: "v2/tailordb-namespace/scripts/transform.js",
@@ -147,6 +147,28 @@ const EXCLUDE_DIRS = new Set([
147
147
  "dist",
148
148
  ".git"
149
149
  ]);
150
+ const ALLOWED_DOT_DIRS = new Set([".github", ".circleci"]);
151
+ function shouldSkipDirectory(name) {
152
+ return EXCLUDE_DIRS.has(name) || name.startsWith(".") && !ALLOWED_DOT_DIRS.has(name);
153
+ }
154
+ async function* walkFiles(root, relativeDir = "") {
155
+ const absoluteDir = path.join(root, relativeDir);
156
+ let entries;
157
+ try {
158
+ entries = await fs.promises.readdir(absoluteDir, { withFileTypes: true });
159
+ } catch {
160
+ return;
161
+ }
162
+ for (const entry of entries) {
163
+ const relative = relativeDir ? path.join(relativeDir, entry.name) : entry.name;
164
+ if (entry.isDirectory()) {
165
+ if (shouldSkipDirectory(entry.name)) continue;
166
+ yield* walkFiles(root, relative);
167
+ continue;
168
+ }
169
+ if (entry.isFile()) yield relative;
170
+ }
171
+ }
150
172
  /**
151
173
  * Print a colorized unified diff for a single file to stderr.
152
174
  * @param filePath - Absolute path to the file
@@ -176,6 +198,13 @@ async function loadTransform(scriptPath) {
176
198
  if (typeof mod.default !== "function") throw new Error(`Transform at ${scriptPath} does not have a default export function`);
177
199
  return mod.default;
178
200
  }
201
+ function legacyPatternWarnings(relative, content, transforms) {
202
+ return transforms.flatMap((lt) => {
203
+ const found = lt.legacyPatterns.filter((p) => content.includes(p));
204
+ if (found.length === 0) return [];
205
+ return [`${relative}: contains ${found.join(", ")} but was not migrated automatically (rule: ${lt.id}). Manual migration may be needed.`];
206
+ });
207
+ }
179
208
  /**
180
209
  * Run multiple codemods on a project directory using in-memory chaining.
181
210
  * Each file is processed through all transforms whose filePatterns match it.
@@ -194,23 +223,20 @@ async function runCodemods(codemods, targetPath, dryRun) {
194
223
  loaded.push({
195
224
  id: codemod.id,
196
225
  transform: await loadTransform(scriptPath),
197
- matches: picomatch(patterns),
226
+ matches: picomatch(patterns, { dot: true }),
198
227
  legacyPatterns: codemod.legacyPatterns ?? []
199
228
  });
200
229
  }
201
- const allPatterns = /* @__PURE__ */ new Set();
202
- for (const { codemod } of codemods) for (const p of codemod.filePatterns ?? DEFAULT_FILE_PATTERNS) allPatterns.add(p);
203
230
  const filesModified = [];
204
231
  const warnings = [];
205
232
  const appliedCodemodIds = /* @__PURE__ */ new Set();
206
233
  const seen = /* @__PURE__ */ new Set();
207
- for (const pattern of allPatterns) for await (const relative of glob(pattern, {
208
- cwd: targetPath,
209
- exclude: (name) => EXCLUDE_DIRS.has(name)
210
- })) {
234
+ for await (const relative of walkFiles(targetPath)) {
211
235
  const absolute = path.resolve(targetPath, relative);
212
236
  if (seen.has(absolute)) continue;
213
237
  seen.add(absolute);
238
+ const matchedTransforms = loaded.filter((lt) => lt.matches(relative));
239
+ if (matchedTransforms.length === 0) continue;
214
240
  let original;
215
241
  try {
216
242
  original = await fs.promises.readFile(absolute, "utf-8");
@@ -218,10 +244,7 @@ async function runCodemods(codemods, targetPath, dryRun) {
218
244
  continue;
219
245
  }
220
246
  let current = original;
221
- const matchedTransforms = [];
222
- for (const lt of loaded) {
223
- if (!lt.matches(relative)) continue;
224
- matchedTransforms.push(lt);
247
+ for (const lt of matchedTransforms) {
225
248
  const result = await lt.transform(current, absolute);
226
249
  if (result != null) {
227
250
  current = result;
@@ -232,10 +255,8 @@ async function runCodemods(codemods, targetPath, dryRun) {
232
255
  filesModified.push(absolute);
233
256
  if (dryRun) printDiff(absolute, original, current);
234
257
  else await fs.promises.writeFile(absolute, current, "utf-8");
235
- } else for (const lt of matchedTransforms) {
236
- const found = lt.legacyPatterns.filter((p) => original.includes(p));
237
- if (found.length > 0) warnings.push(`${relative}: contains ${found.join(", ")} but was not migrated automatically (rule: ${lt.id}). Manual migration may be needed.`);
238
258
  }
259
+ warnings.push(...legacyPatternWarnings(relative, current, matchedTransforms));
239
260
  }
240
261
  return {
241
262
  changed: filesModified.length > 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk-codemod",
3
- "version": "0.3.0-next.0",
3
+ "version": "0.3.0-next.1",
4
4
  "description": "Codemod runner for Tailor Platform SDK upgrades",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,12 +22,12 @@
22
22
  "pathe": "2.0.3",
23
23
  "picomatch": "4.0.4",
24
24
  "pkg-types": "2.3.1",
25
- "politty": "0.5.1",
26
- "semver": "7.8.3",
25
+ "politty": "0.6.0",
26
+ "semver": "7.8.4",
27
27
  "zod": "4.4.3"
28
28
  },
29
29
  "devDependencies": {
30
- "@types/node": "24.13.1",
30
+ "@types/node": "24.13.2",
31
31
  "@types/picomatch": "4.0.3",
32
32
  "@types/semver": "7.7.1",
33
33
  "oxlint": "1.69.0",