@tailor-platform/sdk-codemod 0.3.0-next.7 → 0.3.0-next.8

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,5 +1,25 @@
1
1
  # @tailor-platform/sdk-codemod
2
2
 
3
+ ## 0.3.0-next.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1811](https://github.com/tailor-platform/sdk/pull/1811) [`b2fc104`](https://github.com/tailor-platform/sdk/commit/b2fc104d9cdfc52e98c97bc18d80a9e2e9d5f4c2) Thanks [@toiroakr](https://github.com/toiroakr)! - Move the TailorDB `erdSite` setting out of the core config schema into the ERD plugin's own configuration. `db.<namespace>.erdSite` is no longer accepted in `tailor.config.ts`; configure the ERD deploy target on the plugin instead:
8
+
9
+ ```ts
10
+ import { definePlugins } from "@tailor-platform/sdk";
11
+ import { tailordbErdPlugin } from "@tailor-platform/sdk-plugin-tailordb-erd";
12
+
13
+ export const plugins = definePlugins(
14
+ // TailorDB namespace name → static website name
15
+ tailordbErdPlugin({ sites: { tailordb: "my-erd-site" } }),
16
+ );
17
+ ```
18
+
19
+ The `tailor tailordb erd` commands resolve deploy targets from `tailordbErdPlugin({ sites })` and now validate each namespace against `config.db` and each site name against `staticWebsites`, so typos surface when the config is loaded instead of at deploy time. The `v2/erd-site-to-plugin` codemod migrates existing configs automatically. For programmatic users, `loadTailorDBNamespaces()` additionally returns the config module's registered `plugins`, and namespace selector callbacks receive them as a second argument.
20
+
21
+ - [#1807](https://github.com/tailor-platform/sdk/pull/1807) [`817454f`](https://github.com/tailor-platform/sdk/commit/817454fff35e4093bce5fdcb9e1fcda8bbd1d7ef) Thanks [@dqn](https://github.com/dqn)! - `seedPlugin` no longer generates the `exec.mjs` seed runner. Seeding and validation move to the `tailor seed` commands provided by the `@tailor-platform/sdk-plugin-seed` CLI plugin: install it with `npm install -D @tailor-platform/sdk-plugin-seed`, replace `node <distPath>/exec.mjs` with `tailor seed apply` and `node <distPath>/exec.mjs validate` with `tailor seed validate`, and delete the stale generated `exec.mjs`. Seed data and schema generation (`data/*.jsonl`, `data/*.schema.ts`) is unchanged. Because the plugin reads the config at run time, `machineUserName` changes in seedPlugin options now take effect without regenerating. `@tailor-platform/sdk/cli` gains `loadSeedContext` (and `SeedContext` types) for this, `SeedData` is now JSON-typed, and `executeScript` accepts a plain object `invoker` (`ScriptInvoker`).
22
+
3
23
  ## 0.3.0-next.7
4
24
 
5
25
  ### Patch Changes
@@ -0,0 +1,195 @@
1
+ import { Lang, parse } from "@ast-grep/napi";
2
+ //#region codemods/v2/erd-site-to-plugin/scripts/transform.ts
3
+ const PLUGIN_IMPORT = "import { tailordbErdPlugin } from \"@tailor-platform/sdk-plugin-tailordb-erd\";";
4
+ const DEFINE_PLUGINS_IMPORT = "import { definePlugins } from \"@tailor-platform/sdk\";";
5
+ const SDK_VALUE_IMPORT_REGEX = /(^|\n)import\s*\{[^}\n]*\}\s*from\s*["']@tailor-platform\/sdk["'];?/;
6
+ const FUNCTION_KINDS = /* @__PURE__ */ new Set([
7
+ "arrow_function",
8
+ "function_declaration",
9
+ "function_expression",
10
+ "generator_function",
11
+ "generator_function_declaration",
12
+ "method_definition"
13
+ ]);
14
+ function unquote(text) {
15
+ return text.replace(/^["']|["']$/g, "");
16
+ }
17
+ function propertyName(pair) {
18
+ const key = pair.field("key");
19
+ if (!key || key.kind() === "computed_property_name") return null;
20
+ return unquote(key.text());
21
+ }
22
+ function insideFunction(node) {
23
+ for (let current = node.parent(); current; current = current.parent()) if (FUNCTION_KINDS.has(current.kind())) return true;
24
+ return false;
25
+ }
26
+ /**
27
+ * Resolve the local binding name of `definePlugins` imported from
28
+ * `@tailor-platform/sdk`, honoring `import { definePlugins as alias }`.
29
+ * @param tree - Parsed source tree root.
30
+ * @returns Local binding name, or null when it is not imported.
31
+ */
32
+ function definePluginsLocalName(tree) {
33
+ const importStatements = tree.findAll({ rule: {
34
+ kind: "import_statement",
35
+ has: {
36
+ kind: "string",
37
+ regex: "^[\"']@tailor-platform/sdk[\"']$"
38
+ }
39
+ } });
40
+ for (const statement of importStatements) for (const specifier of statement.findAll({ rule: { kind: "import_specifier" } })) {
41
+ const name = specifier.field("name");
42
+ if (name?.text() !== "definePlugins") continue;
43
+ return (specifier.field("alias") ?? name).text();
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Build an edit that removes a property pair from an object literal, cleaning
49
+ * up the separating comma, an inline line comment that documented the removed
50
+ * property, and the removed line's indentation.
51
+ * @param objectNode - Object literal containing the pair.
52
+ * @param pairNode - Property pair to remove.
53
+ * @returns Edit replacing the object literal with the pair removed.
54
+ */
55
+ function removePairEdit(objectNode, pairNode) {
56
+ const objText = objectNode.text();
57
+ const objStart = objectNode.range().start.index;
58
+ const start = pairNode.range().start.index - objStart;
59
+ const end = pairNode.range().end.index - objStart;
60
+ const before = objText.slice(0, start);
61
+ const after = objText.slice(end);
62
+ let removeFrom = start;
63
+ let removeTo = end;
64
+ const trailing = after.match(/^[ \t]*,[ \t]*(?:\/\/[^\n]*)?\n?/);
65
+ if (trailing) {
66
+ removeTo = end + trailing[0].length;
67
+ const indent = before.match(/\n[ \t]*$/);
68
+ if (indent) removeFrom = start - (indent[0].length - 1);
69
+ } else {
70
+ const leading = before.match(/,\s*$/);
71
+ if (leading) removeFrom = start - leading[0].length;
72
+ const inlineComment = after.match(/^[ \t]*\/\/[^\n]*/);
73
+ if (inlineComment) removeTo = end + inlineComment[0].length;
74
+ }
75
+ return objectNode.replace(objText.slice(0, removeFrom) + objText.slice(removeTo));
76
+ }
77
+ /**
78
+ * Build an edit that appends an argument to a call expression, preserving the
79
+ * call's single-line or multi-line formatting. Insertion points are derived
80
+ * from argument-list AST nodes so a trailing line comment after the last
81
+ * argument cannot swallow the separating comma.
82
+ * @param callNode - Call expression to extend.
83
+ * @param arg - Argument expression to append.
84
+ * @returns Edit replacing the call expression with the argument appended.
85
+ */
86
+ function appendArgEdit(callNode, arg) {
87
+ const callText = callNode.text();
88
+ const base = callNode.range().start.index;
89
+ const children = callNode.field("arguments").children();
90
+ const args = children.filter((child) => child.isNamed() && child.kind() !== "comment");
91
+ const closeOffset = children.at(-1).range().start.index - base;
92
+ const multiline = callText.includes("\n");
93
+ const closeIndent = callText.slice(0, closeOffset).match(/\n([ \t]*)$/)?.[1] ?? "";
94
+ const argIndent = `${closeIndent} `;
95
+ if (args.length === 0) {
96
+ const head = callText.slice(0, closeOffset).replace(/[ \t]*$/, "");
97
+ const rewritten = multiline ? `${head}${argIndent}${arg},\n${closeIndent})` : `${head}${arg})`;
98
+ return callNode.replace(rewritten);
99
+ }
100
+ const lastArg = args.at(-1);
101
+ const followers = children.slice(children.indexOf(lastArg) + 1);
102
+ const trailingComma = followers.find((child) => !child.isNamed() && child.text() === ",");
103
+ const anchor = trailingComma ?? lastArg;
104
+ if (!multiline) {
105
+ const insertAt = anchor.range().end.index - base;
106
+ const insertion = `${trailingComma ? "" : ","} ${arg}`;
107
+ return callNode.replace(callText.slice(0, insertAt) + insertion + callText.slice(insertAt));
108
+ }
109
+ const argInsertAt = (followers.findLast((child) => child.kind() === "comment" && child.range().start.index >= anchor.range().end.index && child.range().start.line === anchor.range().end.line) ?? anchor).range().end.index - base;
110
+ let rewritten = callText.slice(0, argInsertAt) + `\n${argIndent}${arg},` + callText.slice(argInsertAt);
111
+ if (!trailingComma) {
112
+ const commaAt = lastArg.range().end.index - base;
113
+ rewritten = rewritten.slice(0, commaAt) + "," + rewritten.slice(commaAt);
114
+ }
115
+ return callNode.replace(rewritten);
116
+ }
117
+ /**
118
+ * Add `definePlugins` to an existing single-line value import from
119
+ * `@tailor-platform/sdk`, or return null when no such import exists.
120
+ * @param source - Source code to modify.
121
+ * @returns Modified source, or null when a separate import line is needed.
122
+ */
123
+ function addDefinePluginsSpecifier(source) {
124
+ const match = source.match(SDK_VALUE_IMPORT_REGEX);
125
+ if (!match) return null;
126
+ const updated = match[0].replace(/,?\s*\}/, ", definePlugins }");
127
+ return source.replace(match[0], updated);
128
+ }
129
+ function insertImports(source, importLines) {
130
+ const sdkImportRegex = /^import\s+.*from\s+["']@tailor-platform\/sdk[^"']*["'];?$/gm;
131
+ let lastMatch = null;
132
+ for (let match = sdkImportRegex.exec(source); match; match = sdkImportRegex.exec(source)) lastMatch = match;
133
+ const block = importLines.join("\n");
134
+ if (lastMatch) {
135
+ const insertPos = lastMatch.index + lastMatch[0].length;
136
+ return source.slice(0, insertPos) + "\n" + block + source.slice(insertPos);
137
+ }
138
+ return block + "\n" + source;
139
+ }
140
+ /**
141
+ * Move `db.<namespace>.erdSite` entries in defineConfig() into a
142
+ * `tailordbErdPlugin({ sites })` argument of definePlugins():
143
+ *
144
+ * 1. Remove each `erdSite` property from `db.<namespace>` objects of
145
+ * top-level defineConfig() calls (factory-wrapped configs are left for
146
+ * manual review, since their erdSite values may reference local bindings)
147
+ * 2. Append `tailordbErdPlugin({ sites: { <namespace>: <value> } })` to the
148
+ * existing definePlugins() call (honoring an import alias), or add a
149
+ * `plugins` export when none exists
150
+ * 3. Add the plugin import (and a definePlugins import when newly needed)
151
+ * @param source - Source code to transform
152
+ * @returns Transformed source or null if no changes needed
153
+ */
154
+ function transform(source) {
155
+ if (!source.includes("erdSite") || !source.includes("@tailor-platform/sdk")) return null;
156
+ if (source.includes("tailordbErdPlugin")) return null;
157
+ const tree = parse(Lang.TypeScript, source).root();
158
+ const edits = [];
159
+ const siteEntries = [];
160
+ for (const call of tree.findAll({ rule: { pattern: "defineConfig($CONFIG)" } })) {
161
+ if (insideFunction(call)) continue;
162
+ const config = call.getMatch("CONFIG");
163
+ if (!config || config.kind() !== "object") continue;
164
+ const dbObject = config.children().find((child) => child.kind() === "pair" && propertyName(child) === "db")?.field("value");
165
+ if (!dbObject || dbObject.kind() !== "object") continue;
166
+ for (const nsPair of dbObject.children().filter((child) => child.kind() === "pair")) {
167
+ const nsKey = nsPair.field("key");
168
+ const nsObject = nsPair.field("value");
169
+ if (!nsKey || nsKey.kind() === "computed_property_name") continue;
170
+ if (!nsObject || nsObject.kind() !== "object") continue;
171
+ const erdPair = nsObject.children().find((child) => child.kind() === "pair" && propertyName(child) === "erdSite");
172
+ const valueNode = erdPair?.field("value");
173
+ if (!erdPair || !valueNode) continue;
174
+ siteEntries.push(`${nsKey.text()}: ${valueNode.text()}`);
175
+ edits.push(removePairEdit(nsObject, erdPair));
176
+ }
177
+ }
178
+ if (siteEntries.length === 0) return null;
179
+ const pluginExpr = `tailordbErdPlugin({ sites: { ${siteEntries.join(", ")} } })`;
180
+ const localDefinePlugins = definePluginsLocalName(tree);
181
+ const pluginsCall = tree.find({ rule: { pattern: `${localDefinePlugins ?? "definePlugins"}($$$ARGS)` } });
182
+ if (pluginsCall) edits.push(appendArgEdit(pluginsCall, pluginExpr));
183
+ let result = tree.commitEdits(edits);
184
+ const importLines = [PLUGIN_IMPORT];
185
+ if (!pluginsCall && !localDefinePlugins) {
186
+ const merged = addDefinePluginsSpecifier(result);
187
+ if (merged !== null) result = merged;
188
+ else importLines.push(DEFINE_PLUGINS_IMPORT);
189
+ }
190
+ result = insertImports(result, importLines);
191
+ if (!pluginsCall) result = result.replace(/\s*$/, "\n\n") + `export const plugins = ${localDefinePlugins ?? "definePlugins"}(\n ${pluginExpr},\n);\n`;
192
+ return result;
193
+ }
194
+ //#endregion
195
+ export { transform as default };
package/dist/index.js CHANGED
@@ -119,6 +119,8 @@ const V2_NEXT_2 = "2.0.0-next.2";
119
119
  const V2_NEXT_4 = "2.0.0-next.4";
120
120
  const V2_NEXT_5 = "2.0.0-next.5";
121
121
  const V2_NEXT_6 = "2.0.0-next.6";
122
+ const V2_NEXT_7 = "2.0.0-next.7";
123
+ const V2_NEXT_9 = "2.0.0-next.9";
122
124
  /** All registered codemods, in registration order. */
123
125
  const allCodemods = [
124
126
  {
@@ -855,7 +857,7 @@ const allCodemods = [
855
857
  description: "Rename `Workflow.trigger()` (returned by `createWorkflow()`) and `WorkflowJob.trigger()` (returned by `createWorkflowJob()`) to `.start()`, aligning the SDK's ergonomic verb with the platform's `start*` RPC vocabulary. No codemod ships for this rename: distinguishing a workflow/job `.trigger()` call from an unrelated object's own `.trigger()` method requires resolving the receiver back to a `createWorkflow`/`createWorkflowJob` result across files, which the SDK's own CLI bundler already does for build-time rewriting. Reusing that logic in a standalone script is a nontrivial lift, and — unlike the bundler, which fails loudly when it cannot rewrite a call — a codemod false positive would silently rewrite an unrelated `.trigger()` call with no error. For the call-site volume this rename typically involves, manual review guided by the prompt below is the safer trade-off.",
856
858
  since: "1.0.0",
857
859
  until: "2.0.0",
858
- prereleaseUntil: "2.0.0-next.7",
860
+ prereleaseUntil: V2_NEXT_7,
859
861
  filePatterns: ["**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"],
860
862
  suspiciousPatterns: [".trigger("],
861
863
  examples: [{
@@ -1071,6 +1073,73 @@ const allCodemods = [
1071
1073
  "3. Remove unused `Hooks<F>` / `HookFn<>` type imports"
1072
1074
  ].join("\n")
1073
1075
  },
1076
+ {
1077
+ id: "v2/erd-site-to-plugin",
1078
+ name: "`db.<namespace>.erdSite` → `tailordbErdPlugin({ sites })`",
1079
+ description: "Move the TailorDB `erdSite` setting from `db.<namespace>` in tailor.config.ts into `tailordbErdPlugin({ sites })` from `@tailor-platform/sdk-plugin-tailordb-erd`, registered via definePlugins(). The core config schema no longer accepts `erdSite`; the `tailor tailordb erd` commands read the target static website from the plugin configuration and validate each site name against `staticWebsites`. Install `@tailor-platform/sdk-plugin-tailordb-erd` as a dev dependency: the migrated config imports it, so config loading fails with a module-not-found error until it is installed.",
1080
+ since: "1.0.0",
1081
+ until: "2.0.0",
1082
+ prereleaseUntil: V2_NEXT_9,
1083
+ scriptPath: "v2/erd-site-to-plugin/scripts/transform.js",
1084
+ legacyPatterns: ["erdSite:"],
1085
+ sourceStringLegacyPatterns: ["erdSite"],
1086
+ suspiciousPatterns: [
1087
+ "erdSite:",
1088
+ /\berdSite\s*[,}]/,
1089
+ "tailordbErdPlugin"
1090
+ ],
1091
+ sourceStringSuspiciousPatterns: ["erdSite"],
1092
+ examples: [{
1093
+ before: [
1094
+ "export default defineConfig({",
1095
+ " db: {",
1096
+ " tailordb: {",
1097
+ " files: [\"./tailordb/*.ts\"],",
1098
+ " erdSite: \"my-erd-site\",",
1099
+ " },",
1100
+ " },",
1101
+ "});"
1102
+ ].join("\n"),
1103
+ after: [
1104
+ "import { tailordbErdPlugin } from \"@tailor-platform/sdk-plugin-tailordb-erd\";",
1105
+ "",
1106
+ "export default defineConfig({",
1107
+ " db: {",
1108
+ " tailordb: {",
1109
+ " files: [\"./tailordb/*.ts\"],",
1110
+ " },",
1111
+ " },",
1112
+ "});",
1113
+ "",
1114
+ "export const plugins = definePlugins(",
1115
+ " tailordbErdPlugin({ sites: { tailordb: \"my-erd-site\" } }),",
1116
+ ");"
1117
+ ].join("\n")
1118
+ }],
1119
+ prompt: [
1120
+ "In Tailor SDK v2 the TailorDB `erdSite` setting is removed from the core config",
1121
+ "schema; the ERD deploy target is configured on the ERD CLI plugin instead. The",
1122
+ "codemod rewrites literal `db.<namespace>.erdSite` entries inside top-level",
1123
+ "defineConfig() calls into a `tailordbErdPlugin({ sites: { <namespace>: <value> } })`",
1124
+ "argument of definePlugins(), importing it from @tailor-platform/sdk-plugin-tailordb-erd.",
1125
+ "",
1126
+ "First, for every config that now registers tailordbErdPlugin, make sure",
1127
+ "@tailor-platform/sdk-plugin-tailordb-erd is installed as a dev dependency — the",
1128
+ "migrated config imports it, so config loading fails with ERR_MODULE_NOT_FOUND",
1129
+ "until it is installed.",
1130
+ "",
1131
+ "For any remaining `erdSite` config keys the codemod did not rewrite — a db config",
1132
+ "built dynamically or passed via a variable, quoted or computed keys, spread",
1133
+ "properties, a defineConfig() call inside a factory function, or a file that",
1134
+ "already registers tailordbErdPlugin — move the namespace → static-website-name",
1135
+ "mapping into tailordbErdPlugin({ sites }) and delete the `erdSite` key. For",
1136
+ "factory-built configs, keep any referenced parameters or locals in scope when",
1137
+ "moving the value to the module-level definePlugins() export. Each site name",
1138
+ "must match a static website defined in staticWebsites. Leave unrelated",
1139
+ "identifiers that merely contain the name (e.g. a defineStaticWebSite variable",
1140
+ "named erdSite) unchanged."
1141
+ ].join("\n")
1142
+ },
1074
1143
  {
1075
1144
  id: "v2/generate-watch-flag",
1076
1145
  name: "generate --watch flag removed",
@@ -1107,6 +1176,36 @@ const allCodemods = [
1107
1176
  "single generation pass and resolves once it completes."
1108
1177
  ].join("\n")
1109
1178
  },
1179
+ {
1180
+ id: "v2/seed-exec-to-cli-plugin",
1181
+ name: "Generated seed exec.mjs → tailor seed CLI plugin",
1182
+ description: "`seedPlugin` no longer generates the `exec.mjs` seed runner. Seeding and validation move to the `tailor seed` commands provided by the `@tailor-platform/sdk-plugin-seed` CLI plugin: install it as a devDependency, replace `node <distPath>/exec.mjs` invocations with `tailor seed apply` and `node <distPath>/exec.mjs validate` with `tailor seed validate`, and delete the stale generated `<distPath>/exec.mjs` file. Seed data and schema generation (`data/*.jsonl`, `data/*.schema.ts`) is unchanged, and the `tailor seed apply` options mirror the old script (`--machine-user`, `--namespace`, `--skip-idp`, `--truncate`, `--yes`, type-name arguments).",
1183
+ since: "1.0.0",
1184
+ until: "2.0.0",
1185
+ prereleaseUntil: V2_NEXT_9,
1186
+ filePatterns: ["**/package.json", "**/*.{sh,yml,yaml,md,mjs,ts}"],
1187
+ suspiciousPatterns: ["exec.mjs"],
1188
+ sourceStringSuspiciousPatterns: ["exec.mjs"],
1189
+ examples: [{
1190
+ before: "\"seed\": \"node ./seed/exec.mjs\",\n\"seed:validate\": \"node ./seed/exec.mjs validate\"",
1191
+ after: "\"seed\": \"tailor seed apply\",\n\"seed:validate\": \"tailor seed validate\"",
1192
+ lang: "jsonc"
1193
+ }],
1194
+ prompt: [
1195
+ "seedPlugin no longer generates the exec.mjs seed runner in v2. The tailor seed",
1196
+ "CLI plugin (@tailor-platform/sdk-plugin-seed) replaces it:",
1197
+ "",
1198
+ "- Install @tailor-platform/sdk-plugin-seed as a devDependency next to",
1199
+ " @tailor-platform/sdk.",
1200
+ "- Replace `node <distPath>/exec.mjs [options] [types...]` invocations with",
1201
+ " `tailor seed apply [options] [types...]` (same options: --machine-user/-m,",
1202
+ " --namespace/-n, --skip-idp, --truncate, --yes, and type-name arguments).",
1203
+ "- Replace `node <distPath>/exec.mjs validate [path]` with",
1204
+ " `tailor seed validate [path]`.",
1205
+ "- Delete the stale generated `<distPath>/exec.mjs` file; keep the data/",
1206
+ " directory (JSONL data and generated schemas) as-is."
1207
+ ].join("\n")
1208
+ },
1110
1209
  {
1111
1210
  id: "v2/node-minimum-22-15-0",
1112
1211
  name: "Node.js minimum version raised to 22.15.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/sdk-codemod",
3
- "version": "0.3.0-next.7",
3
+ "version": "0.3.0-next.8",
4
4
  "description": "Codemod runner for Tailor Platform SDK upgrades",
5
5
  "license": "MIT",
6
6
  "repository": {