@standards/cli 0.0.0-stage → 1.0.0-alpha.382
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/LICENSE +111 -0
- package/README.md +85 -2
- package/dist/bin.mjs +2047 -0
- package/dist/builder-shorthand-DPVmGTec.mjs +236 -0
- package/dist/engine-imports-K-yySYo3.mjs +173 -0
- package/dist/palette-D63ag-eX.mjs +148 -0
- package/dist/scopes-Bqw1v3wB.mjs +122 -0
- package/dist/source-files-D2Bdfvhx.mjs +76 -0
- package/package.json +57 -3
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*! © 2025-2026 AND YOU CREATE SAS (RCS Paris 940 190 614). Proprietary — see LICENSE (Standards SDK License 1.0). */
|
|
3
|
+
import { i as rewriteSourceFiles, n as parseSourceFile, t as applyEdits } from "./source-files-D2Bdfvhx.mjs";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
//#region src/codemod/builder-shorthand.ts
|
|
6
|
+
const BUILDERS = /* @__PURE__ */ new Set([
|
|
7
|
+
"text",
|
|
8
|
+
"richtext",
|
|
9
|
+
"number",
|
|
10
|
+
"checkbox",
|
|
11
|
+
"date",
|
|
12
|
+
"phone",
|
|
13
|
+
"currency",
|
|
14
|
+
"status",
|
|
15
|
+
"select",
|
|
16
|
+
"multiselect",
|
|
17
|
+
"location",
|
|
18
|
+
"user",
|
|
19
|
+
"relation",
|
|
20
|
+
"formula",
|
|
21
|
+
"rollup",
|
|
22
|
+
"document",
|
|
23
|
+
"object",
|
|
24
|
+
"nativeObject"
|
|
25
|
+
]);
|
|
26
|
+
const WITH_ID = /* @__PURE__ */ new Set(["object", "nativeObject"]);
|
|
27
|
+
const SOURCE = "defineSchemaSource";
|
|
28
|
+
const STANDARDS_PACKAGE = /^@standards\/(schema|client)$/;
|
|
29
|
+
const RELATIVE_MODULE = /^\.{1,2}\//;
|
|
30
|
+
const COMMENT_IN_CONFIG = "comment in config, rewrite by hand";
|
|
31
|
+
/** Import clauses whose module counts as Standards: the packages, plus relative paths on opt-in. */
|
|
32
|
+
function standardsImports(file, options) {
|
|
33
|
+
const clauses = [];
|
|
34
|
+
for (const statement of file.statements) {
|
|
35
|
+
if (!(ts.isImportDeclaration(statement) && ts.isStringLiteral(statement.moduleSpecifier))) continue;
|
|
36
|
+
const specifier = statement.moduleSpecifier.text;
|
|
37
|
+
if ((STANDARDS_PACKAGE.test(specifier) || options.relativeImports === true && RELATIVE_MODULE.test(specifier)) && statement.importClause) clauses.push(statement.importClause);
|
|
38
|
+
}
|
|
39
|
+
return clauses;
|
|
40
|
+
}
|
|
41
|
+
/** Local identifier → exported builder name, for named imports from Standards. */
|
|
42
|
+
function importedBuilders(clauses) {
|
|
43
|
+
const found = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const clause of clauses) {
|
|
45
|
+
const bindings = clause.namedBindings;
|
|
46
|
+
if (!(bindings && ts.isNamedImports(bindings))) continue;
|
|
47
|
+
for (const element of bindings.elements) {
|
|
48
|
+
const exported = element.propertyName?.text ?? element.name.text;
|
|
49
|
+
if (BUILDERS.has(exported) || exported === SOURCE) found.set(element.name.text, exported);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
}
|
|
54
|
+
/** True when `[start, end)` of the file holds a comment, which a rewrite would drop. */
|
|
55
|
+
function hasComment(file, start, end) {
|
|
56
|
+
const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, file.languageVariant, file.text.slice(start, end));
|
|
57
|
+
for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) if (token === ts.SyntaxKind.SingleLineCommentTrivia || token === ts.SyntaxKind.MultiLineCommentTrivia) return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function readConfig(literal, file, allowed) {
|
|
61
|
+
const values = /* @__PURE__ */ new Map();
|
|
62
|
+
for (const property of literal.properties) {
|
|
63
|
+
if (ts.isSpreadAssignment(property)) return "spread in config";
|
|
64
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
65
|
+
values.set(property.name.text, property.name.text);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (!(ts.isPropertyAssignment(property) && ts.isIdentifier(property.name))) return "unsupported property in config";
|
|
69
|
+
const key = property.name.text;
|
|
70
|
+
if (!allowed.has(key)) return `unsupported key "${key}"`;
|
|
71
|
+
values.set(key, property.initializer.getText(file));
|
|
72
|
+
}
|
|
73
|
+
if (!(values.has("name") && values.has("label"))) return "missing name or label";
|
|
74
|
+
return values;
|
|
75
|
+
}
|
|
76
|
+
function rewriteBuilder(call, exported, file) {
|
|
77
|
+
const [config] = call.arguments;
|
|
78
|
+
if (call.arguments.length !== 1 || config === void 0) return { kind: "none" };
|
|
79
|
+
if (!ts.isObjectLiteralExpression(config)) return {
|
|
80
|
+
kind: "skip",
|
|
81
|
+
reason: "config is not an object literal"
|
|
82
|
+
};
|
|
83
|
+
const values = readConfig(config, file, new Set(WITH_ID.has(exported) ? [
|
|
84
|
+
"name",
|
|
85
|
+
"label",
|
|
86
|
+
"id"
|
|
87
|
+
] : ["name", "label"]));
|
|
88
|
+
if (typeof values === "string") return {
|
|
89
|
+
kind: "skip",
|
|
90
|
+
reason: values
|
|
91
|
+
};
|
|
92
|
+
if (hasComment(file, config.getStart(file), config.getEnd())) return {
|
|
93
|
+
kind: "skip",
|
|
94
|
+
reason: COMMENT_IN_CONFIG
|
|
95
|
+
};
|
|
96
|
+
const edits = [{
|
|
97
|
+
start: config.getStart(file),
|
|
98
|
+
end: config.getEnd(),
|
|
99
|
+
text: `${values.get("name")}, ${values.get("label")}`
|
|
100
|
+
}];
|
|
101
|
+
const id = values.get("id");
|
|
102
|
+
if (id !== void 0) edits.push({
|
|
103
|
+
start: call.getEnd(),
|
|
104
|
+
end: call.getEnd(),
|
|
105
|
+
text: `.id(${id})`
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
kind: "edit",
|
|
109
|
+
edits
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function rewriteSource(call, file) {
|
|
113
|
+
const [id, config] = call.arguments;
|
|
114
|
+
if (call.arguments.length !== 2 || id === void 0 || config === void 0) return { kind: "none" };
|
|
115
|
+
if (!ts.isObjectLiteralExpression(config)) return {
|
|
116
|
+
kind: "skip",
|
|
117
|
+
reason: "config is not an object literal"
|
|
118
|
+
};
|
|
119
|
+
const idText = id.getText(file);
|
|
120
|
+
const hasProperties = config.properties.length > 0;
|
|
121
|
+
const open = config.getStart(file) + 1;
|
|
122
|
+
const spaces = /^[ \t]*/.exec(file.text.slice(open))?.[0].length ?? 0;
|
|
123
|
+
const multiline = /^[ \t]*[\r\n]/.test(file.text.slice(open));
|
|
124
|
+
const head = hasProperties ? `{ id: ${idText},${multiline ? "" : " "}` : `{ id: ${idText} }`;
|
|
125
|
+
const end = hasProperties ? multiline ? open : open + spaces : config.getEnd();
|
|
126
|
+
if (hasComment(file, id.getStart(file), end)) return {
|
|
127
|
+
kind: "skip",
|
|
128
|
+
reason: COMMENT_IN_CONFIG
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
kind: "edit",
|
|
132
|
+
edits: [{
|
|
133
|
+
start: id.getStart(file),
|
|
134
|
+
end,
|
|
135
|
+
text: head
|
|
136
|
+
}]
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function overlaps(x, y) {
|
|
140
|
+
if (x.start === x.end && y.start === y.end) return x.start === y.start;
|
|
141
|
+
return x.start < y.end && y.start < x.end;
|
|
142
|
+
}
|
|
143
|
+
/** The widest call among those whose edits collide with another call's edits. */
|
|
144
|
+
function findOverlapVictim(calls) {
|
|
145
|
+
let victim;
|
|
146
|
+
for (const [i, first] of calls.entries()) for (const second of calls.slice(i + 1)) {
|
|
147
|
+
if (!first.edits.some((x) => second.edits.some((y) => overlaps(x, y)))) continue;
|
|
148
|
+
const wider = first.node.getWidth() >= second.node.getWidth() ? first : second;
|
|
149
|
+
if (victim === void 0 || wider.node.getWidth() > victim.node.getWidth()) victim = wider;
|
|
150
|
+
}
|
|
151
|
+
return victim;
|
|
152
|
+
}
|
|
153
|
+
/** Local names of `import * as x` from Standards. */
|
|
154
|
+
function importedNamespaces(clauses) {
|
|
155
|
+
const found = /* @__PURE__ */ new Set();
|
|
156
|
+
for (const clause of clauses) {
|
|
157
|
+
const bindings = clause.namedBindings;
|
|
158
|
+
if (bindings && ts.isNamespaceImport(bindings)) found.add(bindings.name.text);
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
}
|
|
162
|
+
/** True when a call to `exported` still has the pre-shorthand argument shape. */
|
|
163
|
+
function isLegacyShape(call, exported) {
|
|
164
|
+
if (exported === SOURCE) {
|
|
165
|
+
const config = call.arguments[1];
|
|
166
|
+
return call.arguments.length === 2 && config !== void 0 && ts.isObjectLiteralExpression(config);
|
|
167
|
+
}
|
|
168
|
+
return BUILDERS.has(exported) && call.arguments.length === 1;
|
|
169
|
+
}
|
|
170
|
+
/** Every TS and JS module a schema can live in; declaration files carry no calls. */
|
|
171
|
+
/** Rewrites `fn({ name, label })` builders of one file to `fn(name, label)`. */
|
|
172
|
+
function rewriteBuilderShorthand(text, fileName, options = {}) {
|
|
173
|
+
const { file } = parseSourceFile(text, fileName);
|
|
174
|
+
const clauses = standardsImports(file, options);
|
|
175
|
+
const builders = importedBuilders(clauses);
|
|
176
|
+
const namespaces = importedNamespaces(clauses);
|
|
177
|
+
const calls = [];
|
|
178
|
+
const skipped = [];
|
|
179
|
+
function skip(node, callee, reason) {
|
|
180
|
+
const { line, character } = file.getLineAndCharacterOfPosition(node.getStart(file));
|
|
181
|
+
skipped.push({
|
|
182
|
+
file: fileName,
|
|
183
|
+
line: line + 1,
|
|
184
|
+
column: character + 1,
|
|
185
|
+
callee,
|
|
186
|
+
reason
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function visit(node) {
|
|
190
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
191
|
+
const exported = builders.get(node.expression.text);
|
|
192
|
+
if (exported !== void 0) {
|
|
193
|
+
const outcome = exported === SOURCE ? rewriteSource(node, file) : rewriteBuilder(node, exported, file);
|
|
194
|
+
if (outcome.kind === "edit") calls.push({
|
|
195
|
+
node,
|
|
196
|
+
edits: outcome.edits
|
|
197
|
+
});
|
|
198
|
+
else if (outcome.kind === "skip") skip(node, node.expression.text, outcome.reason);
|
|
199
|
+
}
|
|
200
|
+
} else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && namespaces.has(node.expression.expression.text) && isLegacyShape(node, node.expression.name.text)) skip(node, node.expression.getText(file), "namespace import, rewrite by hand");
|
|
201
|
+
ts.forEachChild(node, visit);
|
|
202
|
+
}
|
|
203
|
+
visit(file);
|
|
204
|
+
let kept = calls;
|
|
205
|
+
for (;;) {
|
|
206
|
+
const victim = findOverlapVictim(kept);
|
|
207
|
+
if (victim === void 0) break;
|
|
208
|
+
skip(victim.node, victim.node.expression.text, "overlapping rewrite");
|
|
209
|
+
kept = kept.filter((call) => call !== victim);
|
|
210
|
+
}
|
|
211
|
+
skipped.sort((x, y) => x.line - y.line || x.column - y.column);
|
|
212
|
+
const changed = kept.length;
|
|
213
|
+
return {
|
|
214
|
+
output: applyEdits(text, kept.flatMap((call) => call.edits)),
|
|
215
|
+
changed,
|
|
216
|
+
skipped
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** Walks `paths`, rewrites every TS file, writes back when `write` is set. */
|
|
220
|
+
async function runBuilderShorthand(paths, options) {
|
|
221
|
+
const report = {
|
|
222
|
+
files: 0,
|
|
223
|
+
changed: 0,
|
|
224
|
+
skipped: []
|
|
225
|
+
};
|
|
226
|
+
const results = await rewriteSourceFiles(paths, options.write, (text, path) => rewriteBuilderShorthand(text, path, options));
|
|
227
|
+
for (const { result } of results) {
|
|
228
|
+
report.skipped.push(...result.skipped);
|
|
229
|
+
if (result.changed === 0) continue;
|
|
230
|
+
report.files += 1;
|
|
231
|
+
report.changed += result.changed;
|
|
232
|
+
}
|
|
233
|
+
return report;
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
export { runBuilderShorthand };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*! © 2025-2026 AND YOU CREATE SAS (RCS Paris 940 190 614). Proprietary — see LICENSE (Standards SDK License 1.0). */
|
|
3
|
+
import { i as rewriteSourceFiles, n as parseSourceFile, t as applyEdits } from "./source-files-D2Bdfvhx.mjs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
//#region src/codemod/engine-imports.ts
|
|
7
|
+
const SCHEMA = "@standards/schema";
|
|
8
|
+
const ENGINE = "@standards/engine";
|
|
9
|
+
/** Subpath entries that moved whole: their specifier is renamed, nothing else. */
|
|
10
|
+
const MOVED_SUBPATHS = /* @__PURE__ */ new Set(["capabilities", "webhooks"]);
|
|
11
|
+
function isSchemaSpecifier(specifier) {
|
|
12
|
+
return specifier === SCHEMA || specifier.startsWith(`${SCHEMA}/`);
|
|
13
|
+
}
|
|
14
|
+
function schemaLiteralOf(node) {
|
|
15
|
+
const literal = node.moduleSpecifier;
|
|
16
|
+
return literal !== void 0 && ts.isStringLiteral(literal) && isSchemaSpecifier(literal.text) ? literal : void 0;
|
|
17
|
+
}
|
|
18
|
+
function namedElements(node) {
|
|
19
|
+
if (ts.isImportDeclaration(node)) {
|
|
20
|
+
const bindings = node.importClause?.namedBindings;
|
|
21
|
+
if (node.importClause?.name !== void 0) return void 0;
|
|
22
|
+
return bindings !== void 0 && ts.isNamedImports(bindings) ? bindings.elements : void 0;
|
|
23
|
+
}
|
|
24
|
+
return node.exportClause !== void 0 && ts.isNamedExports(node.exportClause) ? node.exportClause.elements : void 0;
|
|
25
|
+
}
|
|
26
|
+
function declaration(node, elements, specifier) {
|
|
27
|
+
return `${ts.isImportDeclaration(node) ? "import" : "export"}${(ts.isImportDeclaration(node) ? node.importClause?.isTypeOnly : node.isTypeOnly) ? " type" : ""} { ${elements.join(", ")} } from "${specifier}";`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Moves every name `@standards/schema` no longer exports, and `@standards/engine`
|
|
31
|
+
* does, out of a schema import into an engine import beside it.
|
|
32
|
+
*/
|
|
33
|
+
function rewriteEngineImports(source, fileName, exports) {
|
|
34
|
+
const parsed = parseSourceFile(source, fileName);
|
|
35
|
+
if (parsed.error !== void 0) return {
|
|
36
|
+
output: source,
|
|
37
|
+
changed: 0,
|
|
38
|
+
unresolved: [],
|
|
39
|
+
error: parsed.error
|
|
40
|
+
};
|
|
41
|
+
const edits = [];
|
|
42
|
+
const unresolved = [];
|
|
43
|
+
let moved = 0;
|
|
44
|
+
for (const node of parsed.file.statements) {
|
|
45
|
+
if (!(ts.isImportDeclaration(node) || ts.isExportDeclaration(node))) continue;
|
|
46
|
+
const literal = schemaLiteralOf(node);
|
|
47
|
+
if (literal === void 0) continue;
|
|
48
|
+
const specifier = literal.text;
|
|
49
|
+
const subpath = specifier.slice(18);
|
|
50
|
+
if (MOVED_SUBPATHS.has(subpath)) {
|
|
51
|
+
const quote = literal.getText().charAt(0);
|
|
52
|
+
edits.push({
|
|
53
|
+
start: literal.getStart(),
|
|
54
|
+
end: literal.getEnd(),
|
|
55
|
+
text: `${quote}${ENGINE}/${subpath}${quote}`
|
|
56
|
+
});
|
|
57
|
+
moved += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const elements = namedElements(node);
|
|
61
|
+
if (elements === void 0) {
|
|
62
|
+
if (ts.isExportDeclaration(node) || node.importClause !== void 0) unresolved.push(node.getText());
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const stay = [];
|
|
66
|
+
const go = [];
|
|
67
|
+
for (const element of elements) {
|
|
68
|
+
const name = (element.propertyName ?? element.name).text;
|
|
69
|
+
if (exports.schema(specifier).has(name)) stay.push(element.getText());
|
|
70
|
+
else if (exports.engine.has(name)) go.push(element.getText());
|
|
71
|
+
else {
|
|
72
|
+
unresolved.push(name);
|
|
73
|
+
stay.push(element.getText());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (go.length === 0) continue;
|
|
77
|
+
moved += go.length;
|
|
78
|
+
const text = stay.length === 0 ? declaration(node, go, ENGINE) : `${declaration(node, stay, specifier)}\n${declaration(node, go, ENGINE)}`;
|
|
79
|
+
edits.push({
|
|
80
|
+
start: node.getStart(),
|
|
81
|
+
end: node.getEnd(),
|
|
82
|
+
text
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
function visit(node) {
|
|
86
|
+
if (ts.isImportTypeNode(node) && ts.isLiteralTypeNode(node.argument) && ts.isStringLiteral(node.argument.literal) && isSchemaSpecifier(node.argument.literal.text) && node.qualifier !== void 0) {
|
|
87
|
+
const literal = node.argument.literal;
|
|
88
|
+
const first = ts.isIdentifier(node.qualifier) ? node.qualifier : node.qualifier.left;
|
|
89
|
+
const name = ts.isIdentifier(first) ? first.text : void 0;
|
|
90
|
+
if (name !== void 0 && !exports.schema(literal.text).has(name) && exports.engine.has(name)) {
|
|
91
|
+
const quote = literal.getText().charAt(0);
|
|
92
|
+
edits.push({
|
|
93
|
+
start: literal.getStart(),
|
|
94
|
+
end: literal.getEnd(),
|
|
95
|
+
text: `${quote}${ENGINE}${quote}`
|
|
96
|
+
});
|
|
97
|
+
moved += 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
ts.forEachChild(node, visit);
|
|
101
|
+
}
|
|
102
|
+
visit(parsed.file);
|
|
103
|
+
return {
|
|
104
|
+
output: applyEdits(source, edits),
|
|
105
|
+
changed: moved,
|
|
106
|
+
unresolved
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** The names a package's installed type declarations export, resolved from `root`. */
|
|
110
|
+
function exportedNames(packageName, root) {
|
|
111
|
+
const options = {
|
|
112
|
+
module: ts.ModuleKind.NodeNext,
|
|
113
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
114
|
+
skipLibCheck: true,
|
|
115
|
+
noEmit: true
|
|
116
|
+
};
|
|
117
|
+
const resolved = ts.resolveModuleName(packageName, join(root, "index.ts"), options, ts.sys).resolvedModule?.resolvedFileName;
|
|
118
|
+
if (resolved === void 0) return /* @__PURE__ */ new Set();
|
|
119
|
+
const program = ts.createProgram([resolved], options);
|
|
120
|
+
const file = program.getSourceFile(resolved);
|
|
121
|
+
const symbol = file && program.getTypeChecker().getSymbolAtLocation(file);
|
|
122
|
+
if (symbol === void 0) return /* @__PURE__ */ new Set();
|
|
123
|
+
return new Set(program.getTypeChecker().getExportsOfModule(symbol).map((exported) => exported.name));
|
|
124
|
+
}
|
|
125
|
+
/** Reads both packages as installed under `root`; fails when engine is not installed there. */
|
|
126
|
+
function installedExports(root) {
|
|
127
|
+
const bySpecifier = /* @__PURE__ */ new Map();
|
|
128
|
+
function schema(specifier) {
|
|
129
|
+
let names = bySpecifier.get(specifier);
|
|
130
|
+
if (names === void 0) {
|
|
131
|
+
names = exportedNames(specifier, root);
|
|
132
|
+
bySpecifier.set(specifier, names);
|
|
133
|
+
}
|
|
134
|
+
return names;
|
|
135
|
+
}
|
|
136
|
+
if (schema(SCHEMA).size === 0) return `${SCHEMA} is not installed under ${root}.`;
|
|
137
|
+
const engine = exportedNames(ENGINE, root);
|
|
138
|
+
if (engine.size === 0) return `Add ${ENGINE} to your dependencies and install it, then re-run.`;
|
|
139
|
+
return {
|
|
140
|
+
schema,
|
|
141
|
+
engine
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function runEngineImports(paths, options) {
|
|
145
|
+
const exports = installedExports(resolve(options.root));
|
|
146
|
+
if (typeof exports === "string") return exports;
|
|
147
|
+
const report = {
|
|
148
|
+
files: 0,
|
|
149
|
+
moved: 0,
|
|
150
|
+
unresolved: [],
|
|
151
|
+
failures: []
|
|
152
|
+
};
|
|
153
|
+
const results = await rewriteSourceFiles(paths, options.write, (text, path) => rewriteEngineImports(text, path, exports));
|
|
154
|
+
for (const { path, result } of results) {
|
|
155
|
+
if (result.error !== void 0) {
|
|
156
|
+
report.failures.push({
|
|
157
|
+
file: path,
|
|
158
|
+
message: result.error
|
|
159
|
+
});
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
for (const name of result.unresolved) report.unresolved.push({
|
|
163
|
+
file: path,
|
|
164
|
+
name
|
|
165
|
+
});
|
|
166
|
+
if (result.changed === 0) continue;
|
|
167
|
+
report.files += 1;
|
|
168
|
+
report.moved += result.changed;
|
|
169
|
+
}
|
|
170
|
+
return report;
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
export { runEngineImports };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*! © 2025-2026 AND YOU CREATE SAS (RCS Paris 940 190 614). Proprietary — see LICENSE (Standards SDK License 1.0). */
|
|
3
|
+
import { i as rewriteSourceFiles, n as parseSourceFile, t as applyEdits } from "./source-files-D2Bdfvhx.mjs";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { PALETTE_HUES } from "@standards/constants";
|
|
6
|
+
//#region src/codemod/palette.ts
|
|
7
|
+
/** Previous Tailwind colour id → nearest Standards palette id (same table as the SQL migration). */
|
|
8
|
+
const OLD_TO_NEW = {
|
|
9
|
+
gray: "stone",
|
|
10
|
+
slate: "stone",
|
|
11
|
+
red: "brick",
|
|
12
|
+
rose: "brick",
|
|
13
|
+
orange: "terra",
|
|
14
|
+
amber: "ochre",
|
|
15
|
+
yellow: "ochre",
|
|
16
|
+
lime: "moss",
|
|
17
|
+
green: "sage",
|
|
18
|
+
emerald: "sage",
|
|
19
|
+
teal: "lagoon",
|
|
20
|
+
cyan: "lagoon",
|
|
21
|
+
blue: "ink",
|
|
22
|
+
sky: "ink",
|
|
23
|
+
indigo: "iris",
|
|
24
|
+
violet: "iris",
|
|
25
|
+
purple: "iris",
|
|
26
|
+
pink: "peony",
|
|
27
|
+
fuchsia: "peony"
|
|
28
|
+
};
|
|
29
|
+
const NEUTRAL = "stone";
|
|
30
|
+
/** Fallback order doubles as the tie-break: earlier wins. */
|
|
31
|
+
const HUE_IDS = Object.keys(PALETTE_HUES);
|
|
32
|
+
const PALETTE_IDS = /* @__PURE__ */ new Set([NEUTRAL, ...HUE_IDS]);
|
|
33
|
+
function isStringLike(node) {
|
|
34
|
+
return node !== void 0 && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node));
|
|
35
|
+
}
|
|
36
|
+
function hueDistance(x, y) {
|
|
37
|
+
const delta = Math.abs(x - y) % 360;
|
|
38
|
+
return Math.min(delta, 360 - delta);
|
|
39
|
+
}
|
|
40
|
+
/** Smallest circular hue distance wins; a tie goes to the earlier id of the palette. */
|
|
41
|
+
function nearestFree(target, taken) {
|
|
42
|
+
const hue = PALETTE_HUES[target];
|
|
43
|
+
if (hue === void 0) return void 0;
|
|
44
|
+
let best;
|
|
45
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
46
|
+
for (const id of HUE_IDS) {
|
|
47
|
+
if (taken.has(id)) continue;
|
|
48
|
+
const distance = hueDistance(hue, PALETTE_HUES[id]);
|
|
49
|
+
if (distance < bestDistance) {
|
|
50
|
+
best = id;
|
|
51
|
+
bestDistance = distance;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return best;
|
|
55
|
+
}
|
|
56
|
+
function propertyName(property) {
|
|
57
|
+
if (!(ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property))) return void 0;
|
|
58
|
+
const { name } = property;
|
|
59
|
+
return ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : void 0;
|
|
60
|
+
}
|
|
61
|
+
/** The string literal holding the `color` of an object literal, if it has one. */
|
|
62
|
+
function colorLiteral(object) {
|
|
63
|
+
if (!ts.isObjectLiteralExpression(object)) return void 0;
|
|
64
|
+
for (const property of object.properties) if (ts.isPropertyAssignment(property) && propertyName(property) === "color") return isStringLike(property.initializer) ? property.initializer : void 0;
|
|
65
|
+
}
|
|
66
|
+
/** Every string literal that carries a colour id: `color` values, `variant` attributes, `.color()` arguments. */
|
|
67
|
+
function colourLiterals(file) {
|
|
68
|
+
const found = [];
|
|
69
|
+
function visit(node) {
|
|
70
|
+
if (ts.isPropertyAssignment(node) && propertyName(node) === "color") {
|
|
71
|
+
if (isStringLike(node.initializer)) found.push(node.initializer);
|
|
72
|
+
} else if (ts.isJsxAttribute(node) && node.name.getText(file) === "variant") {
|
|
73
|
+
const value = node.initializer;
|
|
74
|
+
if (value && ts.isStringLiteral(value)) found.push(value);
|
|
75
|
+
else if (value && ts.isJsxExpression(value) && isStringLike(value.expression)) found.push(value.expression);
|
|
76
|
+
} else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "color" && isStringLike(node.arguments[0])) found.push(node.arguments[0]);
|
|
77
|
+
ts.forEachChild(node, visit);
|
|
78
|
+
}
|
|
79
|
+
visit(file);
|
|
80
|
+
return found;
|
|
81
|
+
}
|
|
82
|
+
/** Rewrites the colour ids of one source file to the Standards palette. */
|
|
83
|
+
function rewritePalette(text, fileName) {
|
|
84
|
+
const { file, error } = parseSourceFile(text, fileName);
|
|
85
|
+
if (error !== void 0) return {
|
|
86
|
+
output: text,
|
|
87
|
+
changed: 0,
|
|
88
|
+
error
|
|
89
|
+
};
|
|
90
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const literal of colourLiterals(file)) {
|
|
92
|
+
const mapped = OLD_TO_NEW[literal.text];
|
|
93
|
+
if (mapped !== void 0) replacements.set(literal, mapped);
|
|
94
|
+
}
|
|
95
|
+
function resolveArray(array) {
|
|
96
|
+
const options = array.elements.map(colorLiteral).filter((literal) => literal !== void 0);
|
|
97
|
+
const taken = new Set(options.map((o) => o.text).filter((id) => PALETTE_IDS.has(id)));
|
|
98
|
+
for (const option of options) {
|
|
99
|
+
const mapped = replacements.get(option);
|
|
100
|
+
if (mapped === void 0) continue;
|
|
101
|
+
const id = taken.has(mapped) ? nearestFree(mapped, taken) ?? mapped : mapped;
|
|
102
|
+
replacements.set(option, id);
|
|
103
|
+
taken.add(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function visitArrays(node) {
|
|
107
|
+
if (ts.isArrayLiteralExpression(node)) resolveArray(node);
|
|
108
|
+
ts.forEachChild(node, visitArrays);
|
|
109
|
+
}
|
|
110
|
+
visitArrays(file);
|
|
111
|
+
const edits = [...replacements.entries()].filter(([literal, id]) => literal.text !== id).map(([literal, id]) => ({
|
|
112
|
+
start: literal.getStart(file) + 1,
|
|
113
|
+
end: literal.getEnd() - 1,
|
|
114
|
+
text: id
|
|
115
|
+
}));
|
|
116
|
+
return {
|
|
117
|
+
output: applyEdits(text, edits),
|
|
118
|
+
changed: edits.length
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** Walks `paths`, rewrites every TS or JS file, writes back when `write` is set. */
|
|
122
|
+
async function runPalette(paths, options) {
|
|
123
|
+
const report = {
|
|
124
|
+
files: 0,
|
|
125
|
+
changed: 0,
|
|
126
|
+
rewritten: [],
|
|
127
|
+
failures: []
|
|
128
|
+
};
|
|
129
|
+
for (const { path, result } of await rewriteSourceFiles(paths, options.write, rewritePalette)) {
|
|
130
|
+
if (result.error !== void 0) {
|
|
131
|
+
report.failures.push({
|
|
132
|
+
file: path,
|
|
133
|
+
message: result.error
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (result.changed === 0) continue;
|
|
138
|
+
report.files += 1;
|
|
139
|
+
report.changed += result.changed;
|
|
140
|
+
report.rewritten.push({
|
|
141
|
+
file: path,
|
|
142
|
+
changed: result.changed
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return report;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
export { runPalette };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*! © 2025-2026 AND YOU CREATE SAS (RCS Paris 940 190 614). Proprietary — see LICENSE (Standards SDK License 1.0). */
|
|
3
|
+
import { r as rewriteFiles } from "./source-files-D2Bdfvhx.mjs";
|
|
4
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
6
|
+
//#region src/codemod/scopes.ts
|
|
7
|
+
/**
|
|
8
|
+
* Every package that left `@stndrds` for `@standards`: the restricted ones
|
|
9
|
+
* (now on the licensed registry only) and the public ones (now on npmjs and
|
|
10
|
+
* the licensed registry). A historical list: packages created later never had
|
|
11
|
+
* an `@stndrds` name.
|
|
12
|
+
*/
|
|
13
|
+
const RENAMED_PACKAGES = [
|
|
14
|
+
"adapter-cache",
|
|
15
|
+
"adapter-kernel",
|
|
16
|
+
"adapter-meilisearch",
|
|
17
|
+
"adapter-nestjs",
|
|
18
|
+
"adapter-oidc-provider",
|
|
19
|
+
"adapter-postgres",
|
|
20
|
+
"adapter-posthog",
|
|
21
|
+
"adapter-redis",
|
|
22
|
+
"adapter-supabase",
|
|
23
|
+
"adapter-vercel-sandbox",
|
|
24
|
+
"auth",
|
|
25
|
+
"blocks",
|
|
26
|
+
"cli",
|
|
27
|
+
"client",
|
|
28
|
+
"constants",
|
|
29
|
+
"ds",
|
|
30
|
+
"engine",
|
|
31
|
+
"fonts",
|
|
32
|
+
"react",
|
|
33
|
+
"runtime",
|
|
34
|
+
"schema",
|
|
35
|
+
"ui"
|
|
36
|
+
];
|
|
37
|
+
const SCOPE_REGISTRY_LINE = `@standards:registry=https://registry.standards.run/`;
|
|
38
|
+
/**
|
|
39
|
+
* The key line belongs in the user-level `~/.npmrc`, never the project one:
|
|
40
|
+
* pnpm does not expand environment variables in registry credentials read
|
|
41
|
+
* from a project `.npmrc`, so `${STANDARDS_TOKEN}` there reaches the
|
|
42
|
+
* registry as no key at all (401). Earlier runs of this codemod wrote it.
|
|
43
|
+
*/
|
|
44
|
+
const PROJECT_AUTH_LINE = /^\s*\/\/registry\.standards\.run\/:_authToken=\$\{[^}]+\}\s*$/;
|
|
45
|
+
/** Public on npmjs too: a project using only these needs no licence and no registry line. */
|
|
46
|
+
const PUBLIC_PACKAGES = /* @__PURE__ */ new Set([
|
|
47
|
+
"cli",
|
|
48
|
+
"client",
|
|
49
|
+
"constants",
|
|
50
|
+
"schema"
|
|
51
|
+
]);
|
|
52
|
+
const LICENSED_NAME = new RegExp(`@standards/(${RENAMED_PACKAGES.filter((name) => !PUBLIC_PACKAGES.has(name)).join("|")})(?![a-z0-9-])`);
|
|
53
|
+
const OLD_NAME = new RegExp(`@stndrds/(${RENAMED_PACKAGES.join("|")})(?![a-z0-9-])`, "g");
|
|
54
|
+
/** Code, styles, manifests, workspace config and agent guidance (AGENTS.md, CLAUDE.md). */
|
|
55
|
+
const REWRITTEN_FILE = /\.(?:[cm]?tsx?|[cm]?jsx?|css|scss|sass|less|json|jsonc|ya?ml|mdx?)$/;
|
|
56
|
+
/** Lockfiles are regenerated by the install that follows, never edited. */
|
|
57
|
+
const LOCKFILES = /* @__PURE__ */ new Set([
|
|
58
|
+
"pnpm-lock.yaml",
|
|
59
|
+
"package-lock.json",
|
|
60
|
+
"yarn.lock",
|
|
61
|
+
"bun.lock"
|
|
62
|
+
]);
|
|
63
|
+
/** Renames every reference to one of our packages from `@stndrds/<name>` to `@standards/<name>`. */
|
|
64
|
+
function rewriteScopes(source) {
|
|
65
|
+
let changed = 0;
|
|
66
|
+
const output = source.replace(OLD_NAME, (_match, name) => {
|
|
67
|
+
changed += 1;
|
|
68
|
+
return `@standards/${name}`;
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
output,
|
|
72
|
+
changed,
|
|
73
|
+
licensed: LICENSED_NAME.test(output)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Project `.npmrc` pointing `@standards` at the licensed registry: a different
|
|
78
|
+
* `@standards:registry` is replaced in place, an env-variable key line for the
|
|
79
|
+
* registry is removed (it never worked there), every other line is kept.
|
|
80
|
+
*/
|
|
81
|
+
function ensureScopesNpmrc(text) {
|
|
82
|
+
const lines = text.split("\n");
|
|
83
|
+
if (lines.at(-1) === "") lines.pop();
|
|
84
|
+
const output = lines.filter((line) => !PROJECT_AUTH_LINE.test(line)).map((line) => line.trim().startsWith("@standards:registry=") ? SCOPE_REGISTRY_LINE : line);
|
|
85
|
+
if (!output.includes(SCOPE_REGISTRY_LINE)) output.push(SCOPE_REGISTRY_LINE);
|
|
86
|
+
return `${output.join("\n")}\n`;
|
|
87
|
+
}
|
|
88
|
+
function isRewritten(path) {
|
|
89
|
+
return REWRITTEN_FILE.test(path) && !LOCKFILES.has(basename(path));
|
|
90
|
+
}
|
|
91
|
+
async function readOptional(path) {
|
|
92
|
+
try {
|
|
93
|
+
return await readFile(path, "utf8");
|
|
94
|
+
} catch {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function runScopes(paths, options) {
|
|
99
|
+
const report = {
|
|
100
|
+
files: 0,
|
|
101
|
+
changed: 0,
|
|
102
|
+
licensed: false
|
|
103
|
+
};
|
|
104
|
+
const results = await rewriteFiles(paths, isRewritten, options.write, rewriteScopes);
|
|
105
|
+
for (const { result } of results) {
|
|
106
|
+
if (result.licensed) report.licensed = true;
|
|
107
|
+
if (result.changed === 0) continue;
|
|
108
|
+
report.files += 1;
|
|
109
|
+
report.changed += result.changed;
|
|
110
|
+
}
|
|
111
|
+
if (!report.licensed) return report;
|
|
112
|
+
const npmrcPath = join(options.root, ".npmrc");
|
|
113
|
+
const current = await readOptional(npmrcPath);
|
|
114
|
+
const next = ensureScopesNpmrc(current);
|
|
115
|
+
if (next !== current) {
|
|
116
|
+
report.npmrc = npmrcPath;
|
|
117
|
+
if (options.write) await writeFile(npmrcPath, next);
|
|
118
|
+
}
|
|
119
|
+
return report;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { runScopes };
|