@tailor-platform/sdk-codemod 0.2.7 → 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 +28 -0
- package/dist/codemods/v2/apply-to-deploy/scripts/transform.js +5 -3
- package/dist/codemods/v2/cli-rename/scripts/transform.js +373 -14
- package/dist/codemods/v2/plugin-cli-import/scripts/transform.js +70 -0
- package/dist/codemods/v2/tailordb-namespace/scripts/transform.js +5 -4
- package/dist/index.js +49 -20
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
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
|
+
|
|
17
|
+
## 0.3.0-next.0
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
|
|
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
|
+
|
|
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.
|
|
30
|
+
|
|
3
31
|
## 0.2.7
|
|
4
32
|
|
|
5
33
|
### Patch Changes
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as path from "pathe";
|
|
2
2
|
//#region codemods/v2/apply-to-deploy/scripts/transform.ts
|
|
3
|
-
const
|
|
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, (
|
|
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
|
|
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
|
|
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
|
|
7
|
-
return value
|
|
11
|
+
function isOptionBoundaryChar(value) {
|
|
12
|
+
return value === void 0 || !/[\w-]/.test(value);
|
|
8
13
|
}
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
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`)
|
|
39
|
-
* (`
|
|
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 };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Lang, parse } from "@ast-grep/napi";
|
|
2
|
+
//#region codemods/v2/plugin-cli-import/scripts/transform.ts
|
|
3
|
+
const CLI_MODULE = "@tailor-platform/sdk/cli";
|
|
4
|
+
/** Deprecated plugin re-exports from `@tailor-platform/sdk/cli` and their dedicated subpaths. */
|
|
5
|
+
const PLUGIN_SUBPATHS = {
|
|
6
|
+
kyselyTypePlugin: "@tailor-platform/sdk/plugin/kysely-type",
|
|
7
|
+
enumConstantsPlugin: "@tailor-platform/sdk/plugin/enum-constants",
|
|
8
|
+
fileUtilsPlugin: "@tailor-platform/sdk/plugin/file-utils",
|
|
9
|
+
seedPlugin: "@tailor-platform/sdk/plugin/seed"
|
|
10
|
+
};
|
|
11
|
+
function* iterateImportSpecs(importStmt) {
|
|
12
|
+
const specs = importStmt.findAll({ rule: { kind: "import_specifier" } });
|
|
13
|
+
for (const spec of specs) {
|
|
14
|
+
const idents = spec.children().filter((c) => c.kind() === "identifier");
|
|
15
|
+
if (idents.length === 0) continue;
|
|
16
|
+
yield {
|
|
17
|
+
importedName: idents[0].text(),
|
|
18
|
+
text: spec.text()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** True for `import type { ... }` (statement-level `type`), not inline `{ type x }`. */
|
|
23
|
+
function isTypeOnlyImport(importStmt) {
|
|
24
|
+
return importStmt.children().some((c) => c.kind() === "type");
|
|
25
|
+
}
|
|
26
|
+
/** True when the import has a default binding or a `* as x` namespace binding. */
|
|
27
|
+
function hasNonNamedBinding(importStmt) {
|
|
28
|
+
const clause = importStmt.children().find((c) => c.kind() === "import_clause");
|
|
29
|
+
if (!clause) return false;
|
|
30
|
+
return clause.children().some((c) => c.kind() === "identifier" || c.kind() === "namespace_import");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite deprecated plugin re-export imports from `@tailor-platform/sdk/cli`
|
|
34
|
+
* to their dedicated plugin subpaths.
|
|
35
|
+
*
|
|
36
|
+
* Plugin specifiers are split into one `import { plugin } from "<subpath>"`
|
|
37
|
+
* statement each; any non-plugin specifiers stay on the original `/cli`
|
|
38
|
+
* import. A statement-level `import type` is carried over to every generated
|
|
39
|
+
* line. `/cli` imports without plugin specifiers are left untouched.
|
|
40
|
+
* @param source - File contents
|
|
41
|
+
* @param filePath - Absolute path to the file (kept for the runner signature)
|
|
42
|
+
* @returns Transformed source or null when nothing matched.
|
|
43
|
+
*/
|
|
44
|
+
function transform(source, _filePath) {
|
|
45
|
+
if (!source.includes(CLI_MODULE)) return null;
|
|
46
|
+
const root = parse(source.includes("</") || source.includes("/>") ? Lang.Tsx : Lang.TypeScript, source).root();
|
|
47
|
+
const edits = [];
|
|
48
|
+
const importStmts = root.findAll({ rule: {
|
|
49
|
+
kind: "import_statement",
|
|
50
|
+
has: {
|
|
51
|
+
kind: "string",
|
|
52
|
+
regex: `^["']${CLI_MODULE}["']$`
|
|
53
|
+
}
|
|
54
|
+
} });
|
|
55
|
+
for (const importStmt of importStmts) {
|
|
56
|
+
if (hasNonNamedBinding(importStmt)) continue;
|
|
57
|
+
const keyword = isTypeOnlyImport(importStmt) ? "import type" : "import";
|
|
58
|
+
const pluginSpecs = [];
|
|
59
|
+
const otherSpecs = [];
|
|
60
|
+
for (const spec of iterateImportSpecs(importStmt)) (Object.hasOwn(PLUGIN_SUBPATHS, spec.importedName) ? pluginSpecs : otherSpecs).push(spec);
|
|
61
|
+
if (pluginSpecs.length === 0) continue;
|
|
62
|
+
const lines = pluginSpecs.map((s) => `${keyword} { ${s.text} } from "${PLUGIN_SUBPATHS[s.importedName]}";`).toSorted();
|
|
63
|
+
if (otherSpecs.length > 0) lines.unshift(`${keyword} { ${otherSpecs.map((s) => s.text).join(", ")} } from "${CLI_MODULE}";`);
|
|
64
|
+
edits.push(importStmt.replace(lines.join("\n")));
|
|
65
|
+
}
|
|
66
|
+
if (edits.length === 0) return null;
|
|
67
|
+
return root.commitEdits(edits);
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
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
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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";
|
|
@@ -23,6 +22,14 @@ const allCodemods = [
|
|
|
23
22
|
scriptPath: "v2/define-generators-to-plugins/scripts/transform.js",
|
|
24
23
|
legacyPatterns: ["defineGenerators"]
|
|
25
24
|
},
|
|
25
|
+
{
|
|
26
|
+
id: "v2/plugin-cli-import",
|
|
27
|
+
name: "@tailor-platform/sdk/cli plugin imports → dedicated subpaths",
|
|
28
|
+
description: "Rewrite deprecated plugin re-export imports (kyselyTypePlugin, enumConstantsPlugin, fileUtilsPlugin, seedPlugin) from `@tailor-platform/sdk/cli` to their dedicated plugin subpaths",
|
|
29
|
+
since: "1.0.0",
|
|
30
|
+
until: "2.0.0",
|
|
31
|
+
scriptPath: "v2/plugin-cli-import/scripts/transform.js"
|
|
32
|
+
},
|
|
26
33
|
{
|
|
27
34
|
id: "v2/test-run-arg-input",
|
|
28
35
|
name: "function test-run --arg input unwrap",
|
|
@@ -67,7 +74,7 @@ const allCodemods = [
|
|
|
67
74
|
{
|
|
68
75
|
id: "v2/apply-to-deploy",
|
|
69
76
|
name: "tailor-sdk apply → tailor-sdk deploy",
|
|
70
|
-
description: "Rewrite `tailor-sdk apply` invocations in package.json scripts, shell scripts, CI configs, and docs to the v2
|
|
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",
|
|
71
78
|
since: "1.0.0",
|
|
72
79
|
until: "2.0.0",
|
|
73
80
|
scriptPath: "v2/apply-to-deploy/scripts/transform.js",
|
|
@@ -79,8 +86,8 @@ const allCodemods = [
|
|
|
79
86
|
},
|
|
80
87
|
{
|
|
81
88
|
id: "v2/cli-rename",
|
|
82
|
-
name: "v2 CLI rename
|
|
83
|
-
description: "Rewrite `tailor-sdk crash-report`
|
|
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",
|
|
84
91
|
since: "1.0.0",
|
|
85
92
|
until: "2.0.0",
|
|
86
93
|
scriptPath: "v2/cli-rename/scripts/transform.js",
|
|
@@ -88,7 +95,8 @@ const allCodemods = [
|
|
|
88
95
|
"**/package.json",
|
|
89
96
|
"**/*.{sh,bash,zsh,yml,yaml}",
|
|
90
97
|
"**/*.md"
|
|
91
|
-
]
|
|
98
|
+
],
|
|
99
|
+
legacyPatterns: ["tailor-sdk crash-report", "--machineuser"]
|
|
92
100
|
},
|
|
93
101
|
{
|
|
94
102
|
id: "v2/auth-invoker-unwrap",
|
|
@@ -102,7 +110,7 @@ const allCodemods = [
|
|
|
102
110
|
{
|
|
103
111
|
id: "v2/tailordb-namespace",
|
|
104
112
|
name: "Tailordb → tailordb (lowercase ambient namespace)",
|
|
105
|
-
description: "Rewrite references to the
|
|
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`.",
|
|
106
114
|
since: "1.0.0",
|
|
107
115
|
until: "2.0.0",
|
|
108
116
|
scriptPath: "v2/tailordb-namespace/scripts/transform.js",
|
|
@@ -139,6 +147,28 @@ const EXCLUDE_DIRS = new Set([
|
|
|
139
147
|
"dist",
|
|
140
148
|
".git"
|
|
141
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
|
+
}
|
|
142
172
|
/**
|
|
143
173
|
* Print a colorized unified diff for a single file to stderr.
|
|
144
174
|
* @param filePath - Absolute path to the file
|
|
@@ -168,6 +198,13 @@ async function loadTransform(scriptPath) {
|
|
|
168
198
|
if (typeof mod.default !== "function") throw new Error(`Transform at ${scriptPath} does not have a default export function`);
|
|
169
199
|
return mod.default;
|
|
170
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
|
+
}
|
|
171
208
|
/**
|
|
172
209
|
* Run multiple codemods on a project directory using in-memory chaining.
|
|
173
210
|
* Each file is processed through all transforms whose filePatterns match it.
|
|
@@ -186,23 +223,20 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
186
223
|
loaded.push({
|
|
187
224
|
id: codemod.id,
|
|
188
225
|
transform: await loadTransform(scriptPath),
|
|
189
|
-
matches: picomatch(patterns),
|
|
226
|
+
matches: picomatch(patterns, { dot: true }),
|
|
190
227
|
legacyPatterns: codemod.legacyPatterns ?? []
|
|
191
228
|
});
|
|
192
229
|
}
|
|
193
|
-
const allPatterns = /* @__PURE__ */ new Set();
|
|
194
|
-
for (const { codemod } of codemods) for (const p of codemod.filePatterns ?? DEFAULT_FILE_PATTERNS) allPatterns.add(p);
|
|
195
230
|
const filesModified = [];
|
|
196
231
|
const warnings = [];
|
|
197
232
|
const appliedCodemodIds = /* @__PURE__ */ new Set();
|
|
198
233
|
const seen = /* @__PURE__ */ new Set();
|
|
199
|
-
for
|
|
200
|
-
cwd: targetPath,
|
|
201
|
-
exclude: (name) => EXCLUDE_DIRS.has(name)
|
|
202
|
-
})) {
|
|
234
|
+
for await (const relative of walkFiles(targetPath)) {
|
|
203
235
|
const absolute = path.resolve(targetPath, relative);
|
|
204
236
|
if (seen.has(absolute)) continue;
|
|
205
237
|
seen.add(absolute);
|
|
238
|
+
const matchedTransforms = loaded.filter((lt) => lt.matches(relative));
|
|
239
|
+
if (matchedTransforms.length === 0) continue;
|
|
206
240
|
let original;
|
|
207
241
|
try {
|
|
208
242
|
original = await fs.promises.readFile(absolute, "utf-8");
|
|
@@ -210,10 +244,7 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
210
244
|
continue;
|
|
211
245
|
}
|
|
212
246
|
let current = original;
|
|
213
|
-
const matchedTransforms
|
|
214
|
-
for (const lt of loaded) {
|
|
215
|
-
if (!lt.matches(relative)) continue;
|
|
216
|
-
matchedTransforms.push(lt);
|
|
247
|
+
for (const lt of matchedTransforms) {
|
|
217
248
|
const result = await lt.transform(current, absolute);
|
|
218
249
|
if (result != null) {
|
|
219
250
|
current = result;
|
|
@@ -224,10 +255,8 @@ async function runCodemods(codemods, targetPath, dryRun) {
|
|
|
224
255
|
filesModified.push(absolute);
|
|
225
256
|
if (dryRun) printDiff(absolute, original, current);
|
|
226
257
|
else await fs.promises.writeFile(absolute, current, "utf-8");
|
|
227
|
-
} else for (const lt of matchedTransforms) {
|
|
228
|
-
const found = lt.legacyPatterns.filter((p) => original.includes(p));
|
|
229
|
-
if (found.length > 0) warnings.push(`${relative}: contains ${found.join(", ")} but was not migrated automatically (rule: ${lt.id}). Manual migration may be needed.`);
|
|
230
258
|
}
|
|
259
|
+
warnings.push(...legacyPatternWarnings(relative, current, matchedTransforms));
|
|
231
260
|
}
|
|
232
261
|
return {
|
|
233
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
|
+
"version": "0.3.0-next.1",
|
|
4
4
|
"description": "Codemod runner for Tailor Platform SDK upgrades",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,15 +22,15 @@
|
|
|
22
22
|
"pathe": "2.0.3",
|
|
23
23
|
"picomatch": "4.0.4",
|
|
24
24
|
"pkg-types": "2.3.1",
|
|
25
|
-
"politty": "0.
|
|
26
|
-
"semver": "7.8.
|
|
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.
|
|
30
|
+
"@types/node": "24.13.2",
|
|
31
31
|
"@types/picomatch": "4.0.3",
|
|
32
32
|
"@types/semver": "7.7.1",
|
|
33
|
-
"oxlint": "1.
|
|
33
|
+
"oxlint": "1.69.0",
|
|
34
34
|
"tsdown": "0.22.2",
|
|
35
35
|
"typescript": "5.9.3",
|
|
36
36
|
"vitest": "4.1.8"
|