@wix/ditto-codegen-public 1.0.483 → 1.0.484
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/dist/opencode-tools/validate.ts +168 -5
- package/dist/out.js +2306 -2137
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin";
|
|
2
|
-
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
|
|
5
6
|
const EDITOR_COMPONENTS_DIR = "src/extensions/site/components";
|
|
@@ -15,12 +16,173 @@ async function containsTypeScriptFile(directory: string): Promise<boolean> {
|
|
|
15
16
|
|
|
16
17
|
for (const entry of entries) {
|
|
17
18
|
const path = join(directory, entry.name);
|
|
18
|
-
if (entry.isDirectory() && (await containsTypeScriptFile(path)))
|
|
19
|
+
if (entry.isDirectory() && (await containsTypeScriptFile(path)))
|
|
20
|
+
return true;
|
|
19
21
|
if (entry.isFile() && /\.tsx?$/.test(entry.name)) return true;
|
|
20
22
|
}
|
|
21
23
|
return false;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const SAFE_SPECIFIER = /^[\w@./-]{1,100}$/;
|
|
27
|
+
|
|
28
|
+
const MISSING_SPECIFIER_FIX: Record<string, string> = {
|
|
29
|
+
contextProvider:
|
|
30
|
+
"add `moduleSpecifier: '<prefix><provider-kebab-case-name>'` to resources.contextSpecifier",
|
|
31
|
+
functionLibrary:
|
|
32
|
+
"add `moduleSpecifier: '<prefix><library-name>'` to resources",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type TypeScript = typeof import("typescript");
|
|
36
|
+
|
|
37
|
+
// The project's own TypeScript (the one its tsc step uses), else ours.
|
|
38
|
+
function loadTypeScript(cwd: string): TypeScript | undefined {
|
|
39
|
+
for (const base of [cwd, process.cwd()]) {
|
|
40
|
+
try {
|
|
41
|
+
return createRequire(join(base, "package.json"))(
|
|
42
|
+
"typescript",
|
|
43
|
+
) as TypeScript;
|
|
44
|
+
} catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Reads moduleSpecifier only from the object passed to contextProvider() or
|
|
52
|
+
// functionLibrary(), so comments, strings and other calls never count. A value
|
|
53
|
+
// is undefined unless it is a plain single-line quoted string.
|
|
54
|
+
function readModuleSpecifiers(
|
|
55
|
+
ts: TypeScript,
|
|
56
|
+
file: string,
|
|
57
|
+
source: string,
|
|
58
|
+
): { kind?: string; values: (string | undefined)[] } {
|
|
59
|
+
const found: { kind?: string; values: (string | undefined)[] } = {
|
|
60
|
+
values: [],
|
|
61
|
+
};
|
|
62
|
+
const visit = (node: import("typescript").Node, inCall: boolean): void => {
|
|
63
|
+
if (ts.isCallExpression(node)) {
|
|
64
|
+
const callee = node.expression;
|
|
65
|
+
const name = ts.isPropertyAccessExpression(callee)
|
|
66
|
+
? callee.name.text
|
|
67
|
+
: ts.isIdentifier(callee)
|
|
68
|
+
? callee.text
|
|
69
|
+
: undefined;
|
|
70
|
+
if (name === "contextProvider" || name === "functionLibrary") {
|
|
71
|
+
found.kind ??= name;
|
|
72
|
+
node.arguments.forEach((argument) => visit(argument, true));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
inCall &&
|
|
78
|
+
(ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node))
|
|
79
|
+
) {
|
|
80
|
+
const key =
|
|
81
|
+
ts.isIdentifier(node.name) || ts.isStringLiteral(node.name)
|
|
82
|
+
? node.name.text
|
|
83
|
+
: undefined;
|
|
84
|
+
if (key === "moduleSpecifier") {
|
|
85
|
+
const value =
|
|
86
|
+
ts.isPropertyAssignment(node) &&
|
|
87
|
+
ts.isStringLiteral(node.initializer) &&
|
|
88
|
+
!/['"`\r\n]/.test(node.initializer.text)
|
|
89
|
+
? node.initializer.text
|
|
90
|
+
: undefined;
|
|
91
|
+
found.values.push(value);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
ts.forEachChild(node, (child) => visit(child, inCall));
|
|
96
|
+
};
|
|
97
|
+
visit(ts.createSourceFile(file, source, ts.ScriptTarget.Latest), false);
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function checkModuleSpecifiers(cwd: string): Promise<string | undefined> {
|
|
102
|
+
const prefix = process.env.MODULE_SPECIFIER_PREFIX;
|
|
103
|
+
if (!prefix) return undefined;
|
|
104
|
+
|
|
105
|
+
const gitEnv = { ...process.env, LC_ALL: "C" };
|
|
106
|
+
const listings = await Promise.all([
|
|
107
|
+
Bun.$`git ls-files --others --exclude-standard -- src`
|
|
108
|
+
.cwd(cwd)
|
|
109
|
+
.env(gitEnv)
|
|
110
|
+
.nothrow()
|
|
111
|
+
.quiet(),
|
|
112
|
+
Bun.$`git diff --cached --name-only --diff-filter=A --relative -- src`
|
|
113
|
+
.cwd(cwd)
|
|
114
|
+
.env(gitEnv)
|
|
115
|
+
.nothrow()
|
|
116
|
+
.quiet(),
|
|
117
|
+
]);
|
|
118
|
+
for (const listing of listings) {
|
|
119
|
+
const stderr = listing.stderr.toString();
|
|
120
|
+
if (listing.exitCode === 0) continue;
|
|
121
|
+
if (
|
|
122
|
+
listing.exitCode === 128 &&
|
|
123
|
+
/^fatal: not a git repository\b/m.test(stderr)
|
|
124
|
+
)
|
|
125
|
+
return undefined;
|
|
126
|
+
return `FAILED at moduleSpecifier prefix check: git exited ${listing.exitCode}:\n${stderr}`;
|
|
127
|
+
}
|
|
128
|
+
const newExtensionFiles = [
|
|
129
|
+
...new Set(
|
|
130
|
+
listings
|
|
131
|
+
.flatMap((listing) => listing.stdout.toString().split("\n"))
|
|
132
|
+
.map((file) => file.trim())
|
|
133
|
+
.filter((file) => file.endsWith(".extension.ts")),
|
|
134
|
+
),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const violations: string[] = [];
|
|
138
|
+
const ts = newExtensionFiles.length > 0 ? loadTypeScript(cwd) : undefined;
|
|
139
|
+
if (newExtensionFiles.length > 0 && !ts) {
|
|
140
|
+
violations.push(
|
|
141
|
+
"- cannot load the project's typescript package to read moduleSpecifier; run npm install",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
for (const file of newExtensionFiles) {
|
|
145
|
+
if (!ts) break;
|
|
146
|
+
const source = await readFile(join(cwd, file), "utf8").catch(() => "");
|
|
147
|
+
const { kind, values } = readModuleSpecifiers(ts, file, source);
|
|
148
|
+
if (!kind) continue;
|
|
149
|
+
if (values.length === 0) {
|
|
150
|
+
violations.push(
|
|
151
|
+
`- ${file}: no moduleSpecifier; ${MISSING_SPECIFIER_FIX[kind].replace("<prefix>", prefix)}`,
|
|
152
|
+
);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
for (const specifier of values) {
|
|
156
|
+
if (specifier === undefined) {
|
|
157
|
+
violations.push(
|
|
158
|
+
`- ${file}: moduleSpecifier is not a plain string literal; write it as '${prefix}<name>'`,
|
|
159
|
+
);
|
|
160
|
+
} else if (specifier.length > 100) {
|
|
161
|
+
violations.push(
|
|
162
|
+
`- ${file}: moduleSpecifier is over 100 characters; shorten it to '${prefix}<name>'`,
|
|
163
|
+
);
|
|
164
|
+
} else if (!specifier.startsWith(prefix)) {
|
|
165
|
+
violations.push(
|
|
166
|
+
SAFE_SPECIFIER.test(specifier) &&
|
|
167
|
+
prefix.length + specifier.length <= 100
|
|
168
|
+
? `- ${file}: '${specifier}' -> '${prefix}${specifier}'`
|
|
169
|
+
: `- ${file}: moduleSpecifier must be '${prefix}<name>' (letters, digits, @ . _ / - only, at most 100 characters)`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (violations.length === 0) return undefined;
|
|
175
|
+
|
|
176
|
+
return [
|
|
177
|
+
"FAILED at moduleSpecifier prefix check:",
|
|
178
|
+
...violations,
|
|
179
|
+
`Every new context provider and function library must declare a moduleSpecifier that starts with '${prefix}'. ` +
|
|
180
|
+
"Module specifiers are unique across all of Wix, so an unprefixed one can belong to " +
|
|
181
|
+
'another app and fail the checkpoint with "Error during component specifier upsert". ' +
|
|
182
|
+
"Only new extensions are checked: never change the moduleSpecifier of an existing one.",
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
|
|
24
186
|
export default tool({
|
|
25
187
|
description:
|
|
26
188
|
"Run project validation: TypeScript check by default, optionally a full build " +
|
|
@@ -74,6 +236,9 @@ export default tool({
|
|
|
74
236
|
}
|
|
75
237
|
steps.push("tsc --noEmit -p .: OK");
|
|
76
238
|
|
|
239
|
+
const specifierError = await checkModuleSpecifiers(cwd);
|
|
240
|
+
if (specifierError) return [...steps, specifierError].join("\n");
|
|
241
|
+
|
|
77
242
|
if (!args.runBuild) {
|
|
78
243
|
steps.push("wix build: skipped (pass runBuild: true to verify build)");
|
|
79
244
|
return steps.join("\n");
|
|
@@ -94,9 +259,7 @@ export default tool({
|
|
|
94
259
|
join(cwd, EDITOR_COMPONENTS_DIR),
|
|
95
260
|
);
|
|
96
261
|
if (!hasEditorComponents) {
|
|
97
|
-
steps.push(
|
|
98
|
-
"wix generate manifest: skipped (no editor components)",
|
|
99
|
-
);
|
|
262
|
+
steps.push("wix generate manifest: skipped (no editor components)");
|
|
100
263
|
return steps.join("\n");
|
|
101
264
|
}
|
|
102
265
|
|