acture-build-tier 1.1.0
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 +190 -0
- package/README.md +112 -0
- package/dist/ast.cjs +146 -0
- package/dist/ast.cjs.map +1 -0
- package/dist/ast.d.cts +65 -0
- package/dist/ast.d.ts +65 -0
- package/dist/ast.js +111 -0
- package/dist/ast.js.map +1 -0
- package/dist/chunk-SWJN6WEL.js +138 -0
- package/dist/chunk-SWJN6WEL.js.map +1 -0
- package/dist/esbuild.cjs +163 -0
- package/dist/esbuild.cjs.map +1 -0
- package/dist/esbuild.d.cts +44 -0
- package/dist/esbuild.d.ts +44 -0
- package/dist/esbuild.js +24 -0
- package/dist/esbuild.js.map +1 -0
- package/dist/index.cjs +141 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/vite.cjs +157 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +37 -0
- package/dist/vite.d.ts +37 -0
- package/dist/vite.js +22 -0
- package/dist/vite.js.map +1 -0
- package/package.json +85 -0
package/dist/ast.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { parseTierDirective } from './chunk-SWJN6WEL.js';
|
|
2
|
+
import { Project, ScriptTarget, SyntaxKind } from 'ts-morph';
|
|
3
|
+
|
|
4
|
+
var INTERNAL_TOKEN_NAME = "__actureInternalToken__";
|
|
5
|
+
var INTERNAL_TOKEN_DECL = `const ${INTERNAL_TOKEN_NAME} = /* @__PURE__ */ Symbol('acture.internal');
|
|
6
|
+
`;
|
|
7
|
+
function transformSourceAst(source) {
|
|
8
|
+
if (!source.includes("defineCommand")) {
|
|
9
|
+
return { code: source, changed: false, applied: [] };
|
|
10
|
+
}
|
|
11
|
+
const project = new Project({
|
|
12
|
+
useInMemoryFileSystem: true,
|
|
13
|
+
compilerOptions: { target: ScriptTarget.ES2022, allowJs: false },
|
|
14
|
+
skipAddingFilesFromTsConfig: true,
|
|
15
|
+
skipFileDependencyResolution: true
|
|
16
|
+
});
|
|
17
|
+
const file = project.createSourceFile("__acture_build_tier__.ts", source);
|
|
18
|
+
const applied = [];
|
|
19
|
+
let internalCount = 0;
|
|
20
|
+
for (const call of findDefineCommandCalls(file)) {
|
|
21
|
+
const spec = firstObjectLiteralArg(call);
|
|
22
|
+
if (!spec) continue;
|
|
23
|
+
if (hasTierProperty(spec)) continue;
|
|
24
|
+
const jsdocBody = readLeadingJsdocBody(call);
|
|
25
|
+
if (!jsdocBody) continue;
|
|
26
|
+
const directive = parseTierDirective(jsdocBody);
|
|
27
|
+
if (!directive) continue;
|
|
28
|
+
const props = [];
|
|
29
|
+
props.push(`tier: ${JSON.stringify(directive.tier)}`);
|
|
30
|
+
if (directive.tier === "deprecated" && directive.reason !== void 0) {
|
|
31
|
+
props.push(`deprecationReason: ${JSON.stringify(directive.reason)}`);
|
|
32
|
+
}
|
|
33
|
+
if (directive.tier === "internal") {
|
|
34
|
+
props.push(`internalToken: ${INTERNAL_TOKEN_NAME}`);
|
|
35
|
+
internalCount++;
|
|
36
|
+
}
|
|
37
|
+
insertPropertiesAtStart(spec, props);
|
|
38
|
+
applied.push(
|
|
39
|
+
directive.reason !== void 0 ? { tier: directive.tier, reason: directive.reason } : { tier: directive.tier }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
let code = file.getFullText();
|
|
43
|
+
if (internalCount > 0) {
|
|
44
|
+
code = INTERNAL_TOKEN_DECL + code;
|
|
45
|
+
}
|
|
46
|
+
return { code, changed: applied.length > 0, applied };
|
|
47
|
+
}
|
|
48
|
+
function findDefineCommandCalls(file) {
|
|
49
|
+
const calls = [];
|
|
50
|
+
file.forEachDescendant((node) => {
|
|
51
|
+
if (node.getKind() !== SyntaxKind.CallExpression) return;
|
|
52
|
+
const call = node;
|
|
53
|
+
if (call.getExpression().getText() !== "defineCommand") return;
|
|
54
|
+
calls.push(call);
|
|
55
|
+
});
|
|
56
|
+
return calls;
|
|
57
|
+
}
|
|
58
|
+
function firstObjectLiteralArg(call) {
|
|
59
|
+
const args = call.getArguments();
|
|
60
|
+
if (args.length === 0) return null;
|
|
61
|
+
const first = args[0];
|
|
62
|
+
if (first.getKind() !== SyntaxKind.ObjectLiteralExpression) return null;
|
|
63
|
+
return first;
|
|
64
|
+
}
|
|
65
|
+
function hasTierProperty(spec) {
|
|
66
|
+
return spec.getProperties().some((p) => {
|
|
67
|
+
if (p.getKind() !== SyntaxKind.PropertyAssignment) return false;
|
|
68
|
+
const name = p.getName?.();
|
|
69
|
+
return name === "tier";
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function readLeadingJsdocBody(node) {
|
|
73
|
+
let target = node;
|
|
74
|
+
while (true) {
|
|
75
|
+
const parent = target.getParent();
|
|
76
|
+
if (!parent) break;
|
|
77
|
+
const kind = parent.getKind();
|
|
78
|
+
if (kind === SyntaxKind.VariableDeclaration || kind === SyntaxKind.VariableDeclarationList || kind === SyntaxKind.VariableStatement || kind === SyntaxKind.ExpressionStatement) {
|
|
79
|
+
target = parent;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const fullText = target.getSourceFile().getFullText();
|
|
85
|
+
const start = target.getFullStart();
|
|
86
|
+
const end = target.getStart();
|
|
87
|
+
const trivia = fullText.slice(start, end);
|
|
88
|
+
const match = /\/\*\*([\s\S]*?)\*\//g;
|
|
89
|
+
let last = null;
|
|
90
|
+
for (let m = match.exec(trivia); m !== null; m = match.exec(trivia)) {
|
|
91
|
+
last = m;
|
|
92
|
+
}
|
|
93
|
+
if (!last) return null;
|
|
94
|
+
return last[1] ?? "";
|
|
95
|
+
}
|
|
96
|
+
function insertPropertiesAtStart(spec, propTexts) {
|
|
97
|
+
const props = [...propTexts];
|
|
98
|
+
for (let i = props.length - 1; i >= 0; i--) {
|
|
99
|
+
spec.insertPropertyAssignment(0, parseStructure(props[i]));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function parseStructure(propText) {
|
|
103
|
+
const eq = propText.indexOf(":");
|
|
104
|
+
const name = propText.slice(0, eq).trim();
|
|
105
|
+
const initializer = propText.slice(eq + 1).trim();
|
|
106
|
+
return { name, initializer };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export { transformSourceAst };
|
|
110
|
+
//# sourceMappingURL=ast.js.map
|
|
111
|
+
//# sourceMappingURL=ast.js.map
|
package/dist/ast.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ast.ts"],"names":[],"mappings":"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","file":"ast.js","sourcesContent":["/**\n * AST-mode tier mirror — the ts-morph companion to the regex\n * `transformSource` in `./transform.ts`.\n *\n * Why an AST mode exists (phase-4-reflection §1, caveat 1): the regex\n * transform handles the 95th-percentile form (JSDoc directly above a\n * `defineCommand({...})` call, with optional declaration prefix). It\n * caps its per-call lookahead at 4000 chars to stay fast, and treats\n * template-substitution braces conservatively. For projects with\n * heavily macro'd source — or where a CI gate wants *AST-level\n * certainty* rather than a fast regex pass — the AST mode is the\n * second entry point.\n *\n * The AST mode produces the same output as the regex transform on\n * every case the regex handles correctly. Where the regex falls\n * through (e.g. 5000-char spec body, template literals containing\n * `${`-delimited substitutions with braces), the AST mode still\n * applies the transformation. The two modes are interchangeable\n * outputs for normal input.\n *\n * Cost: ts-morph is a real dependency (~7 MB). It is declared as an\n * *optional peer* of `acture-build-tier` so consumers who only use\n * the regex mode don't pay for it. Importing this file pulls ts-morph\n * in lazily.\n *\n * Usage:\n *\n * // tsup, vite, etc. — point your plugin at the AST transform:\n * import { transformSourceAst } from 'acture-build-tier/ast';\n *\n * export function actureTierAstPlugin(): Plugin {\n * return {\n * name: 'acture-tier-ast',\n * transform(code, id) {\n * if (!id.endsWith('.ts') && !id.endsWith('.tsx')) return null;\n * const { code: out, changed } = transformSourceAst(code);\n * return changed ? { code: out, map: null } : null;\n * },\n * };\n * }\n *\n * The regex mode at `acture-build-tier/esbuild` and\n * `acture-build-tier/vite` is the documented default. Use AST mode\n * when the regex's lookahead window or string handling is insufficient.\n */\n\nimport {\n Project,\n ScriptTarget,\n SyntaxKind,\n type CallExpression,\n type Node,\n type ObjectLiteralExpression,\n type SourceFile,\n} from 'ts-morph';\nimport { parseTierDirective, type TransformResult } from './transform.js';\n\nconst INTERNAL_TOKEN_NAME = '__actureInternalToken__';\nconst INTERNAL_TOKEN_DECL = `const ${INTERNAL_TOKEN_NAME} = /* @__PURE__ */ Symbol('acture.internal');\\n`;\n\n/**\n * AST-mode counterpart of `transformSource`. Same input/output contract:\n * a `defineCommand({...})` call with a JSDoc tier tag gets `tier`,\n * `deprecationReason`, and/or `internalToken` injected into the spec.\n *\n * Idempotent: if the spec already declares `tier:`, the call is left\n * alone (matches the regex mode's behavior).\n *\n * Conservative: any `defineCommand` call that isn't a direct top-level\n * declaration or expression statement is processed the same way —\n * ts-morph walks ALL CallExpression nodes. The JSDoc lookup uses the\n * compiler's leading-comment-ranges API, which correctly attributes\n * docblocks regardless of how deeply nested the call is.\n */\nexport function transformSourceAst(source: string): TransformResult {\n if (!source.includes('defineCommand')) {\n return { code: source, changed: false, applied: [] };\n }\n\n const project = new Project({\n useInMemoryFileSystem: true,\n compilerOptions: { target: ScriptTarget.ES2022, allowJs: false },\n skipAddingFilesFromTsConfig: true,\n skipFileDependencyResolution: true,\n });\n const file = project.createSourceFile('__acture_build_tier__.ts', source);\n\n const applied: Array<{ tier: 'stable' | 'experimental' | 'internal' | 'deprecated'; reason?: string }> = [];\n let internalCount = 0;\n\n for (const call of findDefineCommandCalls(file)) {\n const spec = firstObjectLiteralArg(call);\n if (!spec) continue;\n if (hasTierProperty(spec)) continue; // idempotent\n\n const jsdocBody = readLeadingJsdocBody(call);\n if (!jsdocBody) continue;\n\n const directive = parseTierDirective(jsdocBody);\n if (!directive) continue;\n\n // Build the property text. We could go through ts-morph's\n // `insertPropertyAssignment` API, but for the small set of property\n // names we inject the text path is simpler and produces predictable\n // output that lines up with the regex mode's emission.\n const props: string[] = [];\n props.push(`tier: ${JSON.stringify(directive.tier)}`);\n if (directive.tier === 'deprecated' && directive.reason !== undefined) {\n props.push(`deprecationReason: ${JSON.stringify(directive.reason)}`);\n }\n if (directive.tier === 'internal') {\n props.push(`internalToken: ${INTERNAL_TOKEN_NAME}`);\n internalCount++;\n }\n\n insertPropertiesAtStart(spec, props);\n\n applied.push(\n directive.reason !== undefined\n ? { tier: directive.tier, reason: directive.reason }\n : { tier: directive.tier },\n );\n }\n\n let code = file.getFullText();\n if (internalCount > 0) {\n code = INTERNAL_TOKEN_DECL + code;\n }\n return { code, changed: applied.length > 0, applied };\n}\n\n/* ───────────────────────── internals ──────────────────────────────── */\n\nfunction findDefineCommandCalls(file: SourceFile): CallExpression[] {\n const calls: CallExpression[] = [];\n file.forEachDescendant((node) => {\n if (node.getKind() !== SyntaxKind.CallExpression) return;\n const call = node as CallExpression;\n if (call.getExpression().getText() !== 'defineCommand') return;\n calls.push(call);\n });\n return calls;\n}\n\nfunction firstObjectLiteralArg(call: CallExpression): ObjectLiteralExpression | null {\n const args = call.getArguments();\n if (args.length === 0) return null;\n const first = args[0]!;\n if (first.getKind() !== SyntaxKind.ObjectLiteralExpression) return null;\n return first as ObjectLiteralExpression;\n}\n\nfunction hasTierProperty(spec: ObjectLiteralExpression): boolean {\n return spec.getProperties().some((p) => {\n if (p.getKind() !== SyntaxKind.PropertyAssignment) return false;\n const name = (p as Node & { getName?: () => string }).getName?.();\n return name === 'tier';\n });\n}\n\n/**\n * Find the *attached* JSDoc block for a node, climbing through wrapping\n * `const x = …` / `export const x = …` declarations. ts-morph's\n * `getLeadingCommentRanges` would also work but doesn't follow these\n * wrappers cleanly.\n */\nfunction readLeadingJsdocBody(node: Node): string | null {\n let target: Node = node;\n // Walk up CallExpression → VariableDeclaration → VariableStatement\n // (or → ExpressionStatement). The JSDoc is attached to the\n // outermost declaration in the source.\n while (true) {\n const parent = target.getParent();\n if (!parent) break;\n const kind = parent.getKind();\n if (\n kind === SyntaxKind.VariableDeclaration ||\n kind === SyntaxKind.VariableDeclarationList ||\n kind === SyntaxKind.VariableStatement ||\n kind === SyntaxKind.ExpressionStatement\n ) {\n target = parent;\n continue;\n }\n break;\n }\n // Read leading trivia.\n const fullText = target.getSourceFile().getFullText();\n const start = target.getFullStart();\n const end = target.getStart();\n const trivia = fullText.slice(start, end);\n // Find LAST JSDoc block in the trivia.\n const match = /\\/\\*\\*([\\s\\S]*?)\\*\\//g;\n let last: RegExpExecArray | null = null;\n for (let m = match.exec(trivia); m !== null; m = match.exec(trivia)) {\n last = m;\n }\n if (!last) return null;\n return last[1] ?? '';\n}\n\nfunction insertPropertiesAtStart(\n spec: ObjectLiteralExpression,\n propTexts: readonly string[],\n): void {\n // We prepend in reverse so the visible order is `tier`, then\n // `deprecationReason`, then `internalToken`.\n const props = [...propTexts];\n for (let i = props.length - 1; i >= 0; i--) {\n spec.insertPropertyAssignment(0, parseStructure(props[i]!));\n }\n}\n\nfunction parseStructure(propText: string): { name: string; initializer: string } {\n const eq = propText.indexOf(':');\n const name = propText.slice(0, eq).trim();\n const initializer = propText.slice(eq + 1).trim();\n return { name, initializer };\n}\n"]}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/transform.ts
|
|
2
|
+
var TIER_PRECEDENCE = /* @__PURE__ */ new Map([
|
|
3
|
+
["stable", 0],
|
|
4
|
+
["experimental", 1],
|
|
5
|
+
["deprecated", 2],
|
|
6
|
+
["internal", 3]
|
|
7
|
+
]);
|
|
8
|
+
function parseTierDirective(jsdocBody) {
|
|
9
|
+
let chosen;
|
|
10
|
+
const stripped = jsdocBody.replace(/^\s*\*\s?/gm, "");
|
|
11
|
+
const stableRe = /@stable\b/;
|
|
12
|
+
const experimentalRe = /@experimental\b/;
|
|
13
|
+
const internalRe = /@internal\b/;
|
|
14
|
+
const deprecatedRe = /@deprecated\b[ \t]*([^\n@]*)/;
|
|
15
|
+
if (stableRe.test(stripped)) chosen = take(chosen, { tier: "stable" });
|
|
16
|
+
if (experimentalRe.test(stripped)) chosen = take(chosen, { tier: "experimental" });
|
|
17
|
+
const depMatch = deprecatedRe.exec(stripped);
|
|
18
|
+
if (depMatch) {
|
|
19
|
+
const reason = (depMatch[1] ?? "").trim();
|
|
20
|
+
chosen = take(chosen, reason.length > 0 ? { tier: "deprecated", reason } : { tier: "deprecated" });
|
|
21
|
+
}
|
|
22
|
+
if (internalRe.test(stripped)) chosen = take(chosen, { tier: "internal" });
|
|
23
|
+
return chosen;
|
|
24
|
+
}
|
|
25
|
+
function take(current, candidate) {
|
|
26
|
+
if (!current) return candidate;
|
|
27
|
+
const cur = TIER_PRECEDENCE.get(current.tier) ?? 0;
|
|
28
|
+
const cand = TIER_PRECEDENCE.get(candidate.tier) ?? 0;
|
|
29
|
+
return cand > cur ? candidate : current;
|
|
30
|
+
}
|
|
31
|
+
function transformSource(source) {
|
|
32
|
+
if (!source.includes("defineCommand")) {
|
|
33
|
+
return { code: source, changed: false, applied: [] };
|
|
34
|
+
}
|
|
35
|
+
const applied = [];
|
|
36
|
+
const out = [];
|
|
37
|
+
let i = 0;
|
|
38
|
+
const len = source.length;
|
|
39
|
+
let internalCount = 0;
|
|
40
|
+
while (i < len) {
|
|
41
|
+
const jsdocStart = source.indexOf("/**", i);
|
|
42
|
+
if (jsdocStart < 0) {
|
|
43
|
+
out.push(source.slice(i));
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
out.push(source.slice(i, jsdocStart));
|
|
47
|
+
const jsdocEnd = source.indexOf("*/", jsdocStart + 3);
|
|
48
|
+
if (jsdocEnd < 0) {
|
|
49
|
+
out.push(source.slice(jsdocStart));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
const jsdocBlock = source.slice(jsdocStart, jsdocEnd + 2);
|
|
53
|
+
out.push(jsdocBlock);
|
|
54
|
+
const afterDocStart = jsdocEnd + 2;
|
|
55
|
+
const lookahead = source.slice(afterDocStart, afterDocStart + 600);
|
|
56
|
+
const dcRe = /^\s*(?:(?:export\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*)?defineCommand\s*\(\s*\{/;
|
|
57
|
+
const m = dcRe.exec(lookahead);
|
|
58
|
+
if (!m) {
|
|
59
|
+
i = afterDocStart;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const tier = parseTierDirective(jsdocBlock.slice(3, -2));
|
|
63
|
+
if (!tier) {
|
|
64
|
+
i = afterDocStart;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const openBraceAbs = afterDocStart + m[0].length;
|
|
68
|
+
const restOfFile = source.slice(openBraceAbs, openBraceAbs + 4e3);
|
|
69
|
+
if (/\btier\s*:/.test(restOfFile.slice(0, indexOfMatchingBrace(restOfFile)))) {
|
|
70
|
+
i = afterDocStart;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
out.push(source.slice(afterDocStart, openBraceAbs));
|
|
74
|
+
const injected = [];
|
|
75
|
+
injected.push(` tier: ${JSON.stringify(tier.tier)},`);
|
|
76
|
+
if (tier.tier === "deprecated" && tier.reason !== void 0) {
|
|
77
|
+
injected.push(` deprecationReason: ${JSON.stringify(tier.reason)},`);
|
|
78
|
+
}
|
|
79
|
+
if (tier.tier === "internal") {
|
|
80
|
+
injected.push(` internalToken: __actureInternalToken__,`);
|
|
81
|
+
internalCount++;
|
|
82
|
+
}
|
|
83
|
+
out.push(injected.join(""));
|
|
84
|
+
applied.push(tier.reason !== void 0 ? { tier: tier.tier, reason: tier.reason } : { tier: tier.tier });
|
|
85
|
+
i = openBraceAbs;
|
|
86
|
+
}
|
|
87
|
+
let code = out.join("");
|
|
88
|
+
if (internalCount > 0) {
|
|
89
|
+
code = INTERNAL_TOKEN_DECL + code;
|
|
90
|
+
}
|
|
91
|
+
return { code, changed: applied.length > 0, applied };
|
|
92
|
+
}
|
|
93
|
+
var INTERNAL_TOKEN_DECL = "const __actureInternalToken__ = /* @__PURE__ */ Symbol('acture.internal');\n";
|
|
94
|
+
function indexOfMatchingBrace(text) {
|
|
95
|
+
let depth = 1;
|
|
96
|
+
let i = 0;
|
|
97
|
+
const len = text.length;
|
|
98
|
+
while (i < len) {
|
|
99
|
+
const ch = text[i];
|
|
100
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
101
|
+
const quote = ch;
|
|
102
|
+
i++;
|
|
103
|
+
while (i < len) {
|
|
104
|
+
if (text[i] === "\\") {
|
|
105
|
+
i += 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (text[i] === quote) {
|
|
109
|
+
i++;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
i++;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
117
|
+
while (i < len && text[i] !== "\n") i++;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
121
|
+
i += 2;
|
|
122
|
+
while (i < len && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
123
|
+
i += 2;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (ch === "{") depth++;
|
|
127
|
+
else if (ch === "}") {
|
|
128
|
+
depth--;
|
|
129
|
+
if (depth === 0) return i;
|
|
130
|
+
}
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
return len;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { parseTierDirective, transformSource };
|
|
137
|
+
//# sourceMappingURL=chunk-SWJN6WEL.js.map
|
|
138
|
+
//# sourceMappingURL=chunk-SWJN6WEL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transform.ts"],"names":[],"mappings":"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","file":"chunk-SWJN6WEL.js","sourcesContent":["/**\n * Pure source-transform logic. The esbuild and Vite plugin wrappers\n * import this and call it from their respective transform hooks.\n *\n * Strategy: regex-based, intentionally conservative. We match a JSDoc\n * block immediately preceding a `defineCommand({ ... })` call, parse\n * the tier tag(s) from the JSDoc, and inject the corresponding\n * properties into the spec object literal.\n *\n * Why regex and not AST: the build step has to be fast (it runs on every\n * .ts file), and the patterns we accept are deliberately narrow — JSDoc\n * directly above the call site, no exotic syntax in between. If a user\n * writes `defineCommand` in a way our regex can't see, they fall back to\n * writing `tier: 'experimental'` explicitly in the spec — that path is\n * documented as the manual fallback.\n *\n * The four recognized tags:\n * @stable\n * @experimental\n * @internal\n * @deprecated [reason text...]\n *\n * Tag precedence: if multiple appear in the same JSDoc, we honour the\n * most-specific-restriction wins: internal > deprecated > experimental > stable.\n */\n\nexport type Tier = 'stable' | 'experimental' | 'internal' | 'deprecated';\n\nexport interface TierDirective {\n readonly tier: Tier;\n /** Non-empty only for `@deprecated <reason>`. */\n readonly reason?: string;\n}\n\nconst TIER_PRECEDENCE: ReadonlyMap<Tier, number> = new Map([\n ['stable', 0],\n ['experimental', 1],\n ['deprecated', 2],\n ['internal', 3],\n]);\n\n/**\n * Parse a JSDoc block body (the text between `/**` and `* /` ) for tier\n * tags. Returns the most-restrictive tag found, or `undefined` if none.\n */\nexport function parseTierDirective(jsdocBody: string): TierDirective | undefined {\n let chosen: TierDirective | undefined;\n // Strip the leading `*` on each line so multi-line JSDoc doesn't break\n // the tag scanner.\n const stripped = jsdocBody.replace(/^\\s*\\*\\s?/gm, '');\n\n const stableRe = /@stable\\b/;\n const experimentalRe = /@experimental\\b/;\n const internalRe = /@internal\\b/;\n // `@deprecated` may carry free-text reason on the same line.\n const deprecatedRe = /@deprecated\\b[ \\t]*([^\\n@]*)/;\n\n if (stableRe.test(stripped)) chosen = take(chosen, { tier: 'stable' });\n if (experimentalRe.test(stripped)) chosen = take(chosen, { tier: 'experimental' });\n const depMatch = deprecatedRe.exec(stripped);\n if (depMatch) {\n const reason = (depMatch[1] ?? '').trim();\n chosen = take(chosen, reason.length > 0 ? { tier: 'deprecated', reason } : { tier: 'deprecated' });\n }\n if (internalRe.test(stripped)) chosen = take(chosen, { tier: 'internal' });\n\n return chosen;\n}\n\nfunction take(\n current: TierDirective | undefined,\n candidate: TierDirective,\n): TierDirective {\n if (!current) return candidate;\n const cur = TIER_PRECEDENCE.get(current.tier) ?? 0;\n const cand = TIER_PRECEDENCE.get(candidate.tier) ?? 0;\n return cand > cur ? candidate : current;\n}\n\nexport interface TransformResult {\n readonly code: string;\n readonly changed: boolean;\n /** Tier directives applied, in order — exposed for testing. */\n readonly applied: ReadonlyArray<{ tier: Tier; reason?: string }>;\n}\n\n/**\n * Find every `/** ... * /` JSDoc block immediately followed by a\n * `defineCommand({ ... })` call, parse the tier tag(s), and inject the\n * tier (plus deprecationReason / internalToken when applicable) into\n * the spec object literal.\n *\n * - `tier` is injected at the head of the spec object.\n * - `deprecationReason` is injected after `tier`.\n * - `internalToken` references a module-scoped Symbol that is declared\n * once at the top of the file (only when at least one `@internal`\n * command is present in the file).\n *\n * Idempotent: if the spec already contains `tier:`, we leave it alone.\n */\nexport function transformSource(source: string): TransformResult {\n // Skip files that obviously do not call defineCommand.\n if (!source.includes('defineCommand')) {\n return { code: source, changed: false, applied: [] };\n }\n\n const applied: Array<{ tier: Tier; reason?: string }> = [];\n // Find every JSDoc block. We scan in order so insertion offsets remain\n // valid as we rebuild the source incrementally.\n const out: string[] = [];\n let i = 0;\n const len = source.length;\n let internalCount = 0;\n\n while (i < len) {\n const jsdocStart = source.indexOf('/**', i);\n if (jsdocStart < 0) {\n out.push(source.slice(i));\n break;\n }\n out.push(source.slice(i, jsdocStart));\n const jsdocEnd = source.indexOf('*/', jsdocStart + 3);\n if (jsdocEnd < 0) {\n out.push(source.slice(jsdocStart));\n break;\n }\n const jsdocBlock = source.slice(jsdocStart, jsdocEnd + 2);\n out.push(jsdocBlock);\n\n // Look ahead for `defineCommand(` after optional whitespace,\n // an optional `export ...`, an optional `const NAME = ` binding.\n const afterDocStart = jsdocEnd + 2;\n const lookahead = source.slice(afterDocStart, afterDocStart + 600);\n // The pattern captures the slice up to and including the opening\n // `{` of the spec object literal.\n // Optional declaration prefix (e.g. `export const NAME = `), then\n // `defineCommand({`. The declaration is optional so bare-call forms\n // are recognized too (`defineCommand({...})` on its own line).\n const dcRe = /^\\s*(?:(?:export\\s+)?(?:const|let|var)\\s+[A-Za-z_$][\\w$]*\\s*=\\s*)?defineCommand\\s*\\(\\s*\\{/;\n const m = dcRe.exec(lookahead);\n if (!m) {\n i = afterDocStart;\n continue;\n }\n const tier = parseTierDirective(jsdocBlock.slice(3, -2));\n if (!tier) {\n i = afterDocStart;\n continue;\n }\n // Compute the absolute position right after the opening `{`.\n // `m.index` is 0 (we anchored with `^`); `m[0].length` is the entire\n // match, ending at the `{` (inclusive).\n const openBraceAbs = afterDocStart + m[0].length;\n // Detect if the spec already declares `tier:` — if so, leave it.\n const restOfFile = source.slice(openBraceAbs, openBraceAbs + 4000);\n if (/\\btier\\s*:/.test(restOfFile.slice(0, indexOfMatchingBrace(restOfFile)))) {\n i = afterDocStart;\n continue;\n }\n\n // Emit everything between end-of-JSDoc and openBraceAbs.\n out.push(source.slice(afterDocStart, openBraceAbs));\n\n // Build the injected fields.\n const injected: string[] = [];\n injected.push(` tier: ${JSON.stringify(tier.tier)},`);\n if (tier.tier === 'deprecated' && tier.reason !== undefined) {\n injected.push(` deprecationReason: ${JSON.stringify(tier.reason)},`);\n }\n if (tier.tier === 'internal') {\n injected.push(` internalToken: __actureInternalToken__,`);\n internalCount++;\n }\n out.push(injected.join(''));\n\n applied.push(tier.reason !== undefined ? { tier: tier.tier, reason: tier.reason } : { tier: tier.tier });\n\n i = openBraceAbs;\n }\n\n let code = out.join('');\n if (internalCount > 0) {\n code = INTERNAL_TOKEN_DECL + code;\n }\n return { code, changed: applied.length > 0, applied };\n}\n\n/** Declared at module top so every `@internal` command in the file\n * shares it. Cross-module callers cannot see it. */\nconst INTERNAL_TOKEN_DECL =\n \"const __actureInternalToken__ = /* @__PURE__ */ Symbol('acture.internal');\\n\";\n\n/**\n * Find the offset of the matching closing brace for a string that\n * starts immediately after an opening `{` (the `{` is NOT in `text`).\n * Returns the index of the `}` (relative to `text`) on success, or\n * `text.length` if no balanced close is found.\n *\n * The scanner is intentionally minimal — it tracks nested `{}` only.\n * It treats string and template literals as opaque (skips matching\n * braces inside them) to a first approximation: handles single-quoted,\n * double-quoted, and backtick strings; does NOT handle template\n * substitutions perfectly (a `${` inside a backtick may foil it). The\n * defineCommand spec object is shallow enough that this is fine in\n * practice; users with exotic templates can write `tier: 'X'` manually.\n */\nfunction indexOfMatchingBrace(text: string): number {\n let depth = 1;\n let i = 0;\n const len = text.length;\n while (i < len) {\n const ch = text[i]!;\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n const quote = ch;\n i++;\n while (i < len) {\n if (text[i] === '\\\\') { i += 2; continue; }\n if (text[i] === quote) { i++; break; }\n i++;\n }\n continue;\n }\n if (ch === '/' && text[i + 1] === '/') {\n // line comment\n while (i < len && text[i] !== '\\n') i++;\n continue;\n }\n if (ch === '/' && text[i + 1] === '*') {\n i += 2;\n while (i < len && !(text[i] === '*' && text[i + 1] === '/')) i++;\n i += 2;\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return i;\n }\n i++;\n }\n return len;\n}\n"]}
|
package/dist/esbuild.cjs
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('fs/promises');
|
|
4
|
+
|
|
5
|
+
// src/esbuild.ts
|
|
6
|
+
|
|
7
|
+
// src/transform.ts
|
|
8
|
+
var TIER_PRECEDENCE = /* @__PURE__ */ new Map([
|
|
9
|
+
["stable", 0],
|
|
10
|
+
["experimental", 1],
|
|
11
|
+
["deprecated", 2],
|
|
12
|
+
["internal", 3]
|
|
13
|
+
]);
|
|
14
|
+
function parseTierDirective(jsdocBody) {
|
|
15
|
+
let chosen;
|
|
16
|
+
const stripped = jsdocBody.replace(/^\s*\*\s?/gm, "");
|
|
17
|
+
const stableRe = /@stable\b/;
|
|
18
|
+
const experimentalRe = /@experimental\b/;
|
|
19
|
+
const internalRe = /@internal\b/;
|
|
20
|
+
const deprecatedRe = /@deprecated\b[ \t]*([^\n@]*)/;
|
|
21
|
+
if (stableRe.test(stripped)) chosen = take(chosen, { tier: "stable" });
|
|
22
|
+
if (experimentalRe.test(stripped)) chosen = take(chosen, { tier: "experimental" });
|
|
23
|
+
const depMatch = deprecatedRe.exec(stripped);
|
|
24
|
+
if (depMatch) {
|
|
25
|
+
const reason = (depMatch[1] ?? "").trim();
|
|
26
|
+
chosen = take(chosen, reason.length > 0 ? { tier: "deprecated", reason } : { tier: "deprecated" });
|
|
27
|
+
}
|
|
28
|
+
if (internalRe.test(stripped)) chosen = take(chosen, { tier: "internal" });
|
|
29
|
+
return chosen;
|
|
30
|
+
}
|
|
31
|
+
function take(current, candidate) {
|
|
32
|
+
if (!current) return candidate;
|
|
33
|
+
const cur = TIER_PRECEDENCE.get(current.tier) ?? 0;
|
|
34
|
+
const cand = TIER_PRECEDENCE.get(candidate.tier) ?? 0;
|
|
35
|
+
return cand > cur ? candidate : current;
|
|
36
|
+
}
|
|
37
|
+
function transformSource(source) {
|
|
38
|
+
if (!source.includes("defineCommand")) {
|
|
39
|
+
return { code: source, changed: false, applied: [] };
|
|
40
|
+
}
|
|
41
|
+
const applied = [];
|
|
42
|
+
const out = [];
|
|
43
|
+
let i = 0;
|
|
44
|
+
const len = source.length;
|
|
45
|
+
let internalCount = 0;
|
|
46
|
+
while (i < len) {
|
|
47
|
+
const jsdocStart = source.indexOf("/**", i);
|
|
48
|
+
if (jsdocStart < 0) {
|
|
49
|
+
out.push(source.slice(i));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
out.push(source.slice(i, jsdocStart));
|
|
53
|
+
const jsdocEnd = source.indexOf("*/", jsdocStart + 3);
|
|
54
|
+
if (jsdocEnd < 0) {
|
|
55
|
+
out.push(source.slice(jsdocStart));
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const jsdocBlock = source.slice(jsdocStart, jsdocEnd + 2);
|
|
59
|
+
out.push(jsdocBlock);
|
|
60
|
+
const afterDocStart = jsdocEnd + 2;
|
|
61
|
+
const lookahead = source.slice(afterDocStart, afterDocStart + 600);
|
|
62
|
+
const dcRe = /^\s*(?:(?:export\s+)?(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*)?defineCommand\s*\(\s*\{/;
|
|
63
|
+
const m = dcRe.exec(lookahead);
|
|
64
|
+
if (!m) {
|
|
65
|
+
i = afterDocStart;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const tier = parseTierDirective(jsdocBlock.slice(3, -2));
|
|
69
|
+
if (!tier) {
|
|
70
|
+
i = afterDocStart;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const openBraceAbs = afterDocStart + m[0].length;
|
|
74
|
+
const restOfFile = source.slice(openBraceAbs, openBraceAbs + 4e3);
|
|
75
|
+
if (/\btier\s*:/.test(restOfFile.slice(0, indexOfMatchingBrace(restOfFile)))) {
|
|
76
|
+
i = afterDocStart;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
out.push(source.slice(afterDocStart, openBraceAbs));
|
|
80
|
+
const injected = [];
|
|
81
|
+
injected.push(` tier: ${JSON.stringify(tier.tier)},`);
|
|
82
|
+
if (tier.tier === "deprecated" && tier.reason !== void 0) {
|
|
83
|
+
injected.push(` deprecationReason: ${JSON.stringify(tier.reason)},`);
|
|
84
|
+
}
|
|
85
|
+
if (tier.tier === "internal") {
|
|
86
|
+
injected.push(` internalToken: __actureInternalToken__,`);
|
|
87
|
+
internalCount++;
|
|
88
|
+
}
|
|
89
|
+
out.push(injected.join(""));
|
|
90
|
+
applied.push(tier.reason !== void 0 ? { tier: tier.tier, reason: tier.reason } : { tier: tier.tier });
|
|
91
|
+
i = openBraceAbs;
|
|
92
|
+
}
|
|
93
|
+
let code = out.join("");
|
|
94
|
+
if (internalCount > 0) {
|
|
95
|
+
code = INTERNAL_TOKEN_DECL + code;
|
|
96
|
+
}
|
|
97
|
+
return { code, changed: applied.length > 0, applied };
|
|
98
|
+
}
|
|
99
|
+
var INTERNAL_TOKEN_DECL = "const __actureInternalToken__ = /* @__PURE__ */ Symbol('acture.internal');\n";
|
|
100
|
+
function indexOfMatchingBrace(text) {
|
|
101
|
+
let depth = 1;
|
|
102
|
+
let i = 0;
|
|
103
|
+
const len = text.length;
|
|
104
|
+
while (i < len) {
|
|
105
|
+
const ch = text[i];
|
|
106
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
107
|
+
const quote = ch;
|
|
108
|
+
i++;
|
|
109
|
+
while (i < len) {
|
|
110
|
+
if (text[i] === "\\") {
|
|
111
|
+
i += 2;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (text[i] === quote) {
|
|
115
|
+
i++;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
123
|
+
while (i < len && text[i] !== "\n") i++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
127
|
+
i += 2;
|
|
128
|
+
while (i < len && !(text[i] === "*" && text[i + 1] === "/")) i++;
|
|
129
|
+
i += 2;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === "{") depth++;
|
|
133
|
+
else if (ch === "}") {
|
|
134
|
+
depth--;
|
|
135
|
+
if (depth === 0) return i;
|
|
136
|
+
}
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
return len;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/esbuild.ts
|
|
143
|
+
function actureBuildTier(options = {}) {
|
|
144
|
+
const filter = options.filter ?? /\.tsx?$/;
|
|
145
|
+
const exclude = options.exclude ?? /node_modules/;
|
|
146
|
+
return {
|
|
147
|
+
name: "acture-build-tier",
|
|
148
|
+
setup(build) {
|
|
149
|
+
build.onLoad({ filter }, async (args) => {
|
|
150
|
+
if (exclude.test(args.path)) return void 0;
|
|
151
|
+
const buf = await promises.readFile(args.path, "utf8");
|
|
152
|
+
const { code, changed } = transformSource(buf);
|
|
153
|
+
if (!changed) return void 0;
|
|
154
|
+
const loader = args.path.endsWith(".tsx") ? "tsx" : "ts";
|
|
155
|
+
return { contents: code, loader };
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
exports.actureBuildTier = actureBuildTier;
|
|
162
|
+
//# sourceMappingURL=esbuild.cjs.map
|
|
163
|
+
//# sourceMappingURL=esbuild.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transform.ts","../src/esbuild.ts"],"names":["readFile"],"mappings":"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","file":"esbuild.cjs","sourcesContent":["/**\n * Pure source-transform logic. The esbuild and Vite plugin wrappers\n * import this and call it from their respective transform hooks.\n *\n * Strategy: regex-based, intentionally conservative. We match a JSDoc\n * block immediately preceding a `defineCommand({ ... })` call, parse\n * the tier tag(s) from the JSDoc, and inject the corresponding\n * properties into the spec object literal.\n *\n * Why regex and not AST: the build step has to be fast (it runs on every\n * .ts file), and the patterns we accept are deliberately narrow — JSDoc\n * directly above the call site, no exotic syntax in between. If a user\n * writes `defineCommand` in a way our regex can't see, they fall back to\n * writing `tier: 'experimental'` explicitly in the spec — that path is\n * documented as the manual fallback.\n *\n * The four recognized tags:\n * @stable\n * @experimental\n * @internal\n * @deprecated [reason text...]\n *\n * Tag precedence: if multiple appear in the same JSDoc, we honour the\n * most-specific-restriction wins: internal > deprecated > experimental > stable.\n */\n\nexport type Tier = 'stable' | 'experimental' | 'internal' | 'deprecated';\n\nexport interface TierDirective {\n readonly tier: Tier;\n /** Non-empty only for `@deprecated <reason>`. */\n readonly reason?: string;\n}\n\nconst TIER_PRECEDENCE: ReadonlyMap<Tier, number> = new Map([\n ['stable', 0],\n ['experimental', 1],\n ['deprecated', 2],\n ['internal', 3],\n]);\n\n/**\n * Parse a JSDoc block body (the text between `/**` and `* /` ) for tier\n * tags. Returns the most-restrictive tag found, or `undefined` if none.\n */\nexport function parseTierDirective(jsdocBody: string): TierDirective | undefined {\n let chosen: TierDirective | undefined;\n // Strip the leading `*` on each line so multi-line JSDoc doesn't break\n // the tag scanner.\n const stripped = jsdocBody.replace(/^\\s*\\*\\s?/gm, '');\n\n const stableRe = /@stable\\b/;\n const experimentalRe = /@experimental\\b/;\n const internalRe = /@internal\\b/;\n // `@deprecated` may carry free-text reason on the same line.\n const deprecatedRe = /@deprecated\\b[ \\t]*([^\\n@]*)/;\n\n if (stableRe.test(stripped)) chosen = take(chosen, { tier: 'stable' });\n if (experimentalRe.test(stripped)) chosen = take(chosen, { tier: 'experimental' });\n const depMatch = deprecatedRe.exec(stripped);\n if (depMatch) {\n const reason = (depMatch[1] ?? '').trim();\n chosen = take(chosen, reason.length > 0 ? { tier: 'deprecated', reason } : { tier: 'deprecated' });\n }\n if (internalRe.test(stripped)) chosen = take(chosen, { tier: 'internal' });\n\n return chosen;\n}\n\nfunction take(\n current: TierDirective | undefined,\n candidate: TierDirective,\n): TierDirective {\n if (!current) return candidate;\n const cur = TIER_PRECEDENCE.get(current.tier) ?? 0;\n const cand = TIER_PRECEDENCE.get(candidate.tier) ?? 0;\n return cand > cur ? candidate : current;\n}\n\nexport interface TransformResult {\n readonly code: string;\n readonly changed: boolean;\n /** Tier directives applied, in order — exposed for testing. */\n readonly applied: ReadonlyArray<{ tier: Tier; reason?: string }>;\n}\n\n/**\n * Find every `/** ... * /` JSDoc block immediately followed by a\n * `defineCommand({ ... })` call, parse the tier tag(s), and inject the\n * tier (plus deprecationReason / internalToken when applicable) into\n * the spec object literal.\n *\n * - `tier` is injected at the head of the spec object.\n * - `deprecationReason` is injected after `tier`.\n * - `internalToken` references a module-scoped Symbol that is declared\n * once at the top of the file (only when at least one `@internal`\n * command is present in the file).\n *\n * Idempotent: if the spec already contains `tier:`, we leave it alone.\n */\nexport function transformSource(source: string): TransformResult {\n // Skip files that obviously do not call defineCommand.\n if (!source.includes('defineCommand')) {\n return { code: source, changed: false, applied: [] };\n }\n\n const applied: Array<{ tier: Tier; reason?: string }> = [];\n // Find every JSDoc block. We scan in order so insertion offsets remain\n // valid as we rebuild the source incrementally.\n const out: string[] = [];\n let i = 0;\n const len = source.length;\n let internalCount = 0;\n\n while (i < len) {\n const jsdocStart = source.indexOf('/**', i);\n if (jsdocStart < 0) {\n out.push(source.slice(i));\n break;\n }\n out.push(source.slice(i, jsdocStart));\n const jsdocEnd = source.indexOf('*/', jsdocStart + 3);\n if (jsdocEnd < 0) {\n out.push(source.slice(jsdocStart));\n break;\n }\n const jsdocBlock = source.slice(jsdocStart, jsdocEnd + 2);\n out.push(jsdocBlock);\n\n // Look ahead for `defineCommand(` after optional whitespace,\n // an optional `export ...`, an optional `const NAME = ` binding.\n const afterDocStart = jsdocEnd + 2;\n const lookahead = source.slice(afterDocStart, afterDocStart + 600);\n // The pattern captures the slice up to and including the opening\n // `{` of the spec object literal.\n // Optional declaration prefix (e.g. `export const NAME = `), then\n // `defineCommand({`. The declaration is optional so bare-call forms\n // are recognized too (`defineCommand({...})` on its own line).\n const dcRe = /^\\s*(?:(?:export\\s+)?(?:const|let|var)\\s+[A-Za-z_$][\\w$]*\\s*=\\s*)?defineCommand\\s*\\(\\s*\\{/;\n const m = dcRe.exec(lookahead);\n if (!m) {\n i = afterDocStart;\n continue;\n }\n const tier = parseTierDirective(jsdocBlock.slice(3, -2));\n if (!tier) {\n i = afterDocStart;\n continue;\n }\n // Compute the absolute position right after the opening `{`.\n // `m.index` is 0 (we anchored with `^`); `m[0].length` is the entire\n // match, ending at the `{` (inclusive).\n const openBraceAbs = afterDocStart + m[0].length;\n // Detect if the spec already declares `tier:` — if so, leave it.\n const restOfFile = source.slice(openBraceAbs, openBraceAbs + 4000);\n if (/\\btier\\s*:/.test(restOfFile.slice(0, indexOfMatchingBrace(restOfFile)))) {\n i = afterDocStart;\n continue;\n }\n\n // Emit everything between end-of-JSDoc and openBraceAbs.\n out.push(source.slice(afterDocStart, openBraceAbs));\n\n // Build the injected fields.\n const injected: string[] = [];\n injected.push(` tier: ${JSON.stringify(tier.tier)},`);\n if (tier.tier === 'deprecated' && tier.reason !== undefined) {\n injected.push(` deprecationReason: ${JSON.stringify(tier.reason)},`);\n }\n if (tier.tier === 'internal') {\n injected.push(` internalToken: __actureInternalToken__,`);\n internalCount++;\n }\n out.push(injected.join(''));\n\n applied.push(tier.reason !== undefined ? { tier: tier.tier, reason: tier.reason } : { tier: tier.tier });\n\n i = openBraceAbs;\n }\n\n let code = out.join('');\n if (internalCount > 0) {\n code = INTERNAL_TOKEN_DECL + code;\n }\n return { code, changed: applied.length > 0, applied };\n}\n\n/** Declared at module top so every `@internal` command in the file\n * shares it. Cross-module callers cannot see it. */\nconst INTERNAL_TOKEN_DECL =\n \"const __actureInternalToken__ = /* @__PURE__ */ Symbol('acture.internal');\\n\";\n\n/**\n * Find the offset of the matching closing brace for a string that\n * starts immediately after an opening `{` (the `{` is NOT in `text`).\n * Returns the index of the `}` (relative to `text`) on success, or\n * `text.length` if no balanced close is found.\n *\n * The scanner is intentionally minimal — it tracks nested `{}` only.\n * It treats string and template literals as opaque (skips matching\n * braces inside them) to a first approximation: handles single-quoted,\n * double-quoted, and backtick strings; does NOT handle template\n * substitutions perfectly (a `${` inside a backtick may foil it). The\n * defineCommand spec object is shallow enough that this is fine in\n * practice; users with exotic templates can write `tier: 'X'` manually.\n */\nfunction indexOfMatchingBrace(text: string): number {\n let depth = 1;\n let i = 0;\n const len = text.length;\n while (i < len) {\n const ch = text[i]!;\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n const quote = ch;\n i++;\n while (i < len) {\n if (text[i] === '\\\\') { i += 2; continue; }\n if (text[i] === quote) { i++; break; }\n i++;\n }\n continue;\n }\n if (ch === '/' && text[i + 1] === '/') {\n // line comment\n while (i < len && text[i] !== '\\n') i++;\n continue;\n }\n if (ch === '/' && text[i + 1] === '*') {\n i += 2;\n while (i < len && !(text[i] === '*' && text[i + 1] === '/')) i++;\n i += 2;\n continue;\n }\n if (ch === '{') depth++;\n else if (ch === '}') {\n depth--;\n if (depth === 0) return i;\n }\n i++;\n }\n return len;\n}\n","/**\n * esbuild plugin. Also works with tsup, which is esbuild under the hood.\n *\n * Usage in a consumer's tsup.config.ts:\n *\n * import { actureBuildTier } from 'acture-build-tier/esbuild';\n * export default defineConfig({\n * entry: ['src/index.ts'],\n * esbuildPlugins: [actureBuildTier()],\n * });\n *\n * The plugin matches `.ts` and `.tsx` files inside the consumer's\n * project (it deliberately does NOT touch `node_modules/`).\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { transformSource } from './transform.js';\n\n/** Minimal subset of the esbuild Plugin shape we depend on, to avoid\n * pulling esbuild's types into our public surface. */\nexport interface ESBuildPlugin {\n name: string;\n setup(build: {\n onLoad(\n options: { filter: RegExp; namespace?: string },\n callback: (args: { path: string }) => Promise<\n | { contents: string; loader: 'ts' | 'tsx'; }\n | undefined\n > | { contents: string; loader: 'ts' | 'tsx'; } | undefined,\n ): void;\n }): void;\n}\n\nexport interface ActureBuildTierOptions {\n /** File-path regex. Defaults to `/\\\\.tsx?$/`. */\n filter?: RegExp;\n /** Skip transform for paths matching this regex. Defaults to a\n * `node_modules` matcher. */\n exclude?: RegExp;\n}\n\n/** esbuild / tsup plugin. */\nexport function actureBuildTier(options: ActureBuildTierOptions = {}): ESBuildPlugin {\n const filter = options.filter ?? /\\.tsx?$/;\n const exclude = options.exclude ?? /node_modules/;\n return {\n name: 'acture-build-tier',\n setup(build) {\n build.onLoad({ filter }, async (args) => {\n if (exclude.test(args.path)) return undefined;\n const buf = await readFile(args.path, 'utf8');\n const { code, changed } = transformSource(buf);\n if (!changed) return undefined;\n const loader: 'ts' | 'tsx' = args.path.endsWith('.tsx') ? 'tsx' : 'ts';\n return { contents: code, loader };\n });\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild plugin. Also works with tsup, which is esbuild under the hood.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a consumer's tsup.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* import { actureBuildTier } from 'acture-build-tier/esbuild';
|
|
7
|
+
* export default defineConfig({
|
|
8
|
+
* entry: ['src/index.ts'],
|
|
9
|
+
* esbuildPlugins: [actureBuildTier()],
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* The plugin matches `.ts` and `.tsx` files inside the consumer's
|
|
13
|
+
* project (it deliberately does NOT touch `node_modules/`).
|
|
14
|
+
*/
|
|
15
|
+
/** Minimal subset of the esbuild Plugin shape we depend on, to avoid
|
|
16
|
+
* pulling esbuild's types into our public surface. */
|
|
17
|
+
interface ESBuildPlugin {
|
|
18
|
+
name: string;
|
|
19
|
+
setup(build: {
|
|
20
|
+
onLoad(options: {
|
|
21
|
+
filter: RegExp;
|
|
22
|
+
namespace?: string;
|
|
23
|
+
}, callback: (args: {
|
|
24
|
+
path: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
contents: string;
|
|
27
|
+
loader: 'ts' | 'tsx';
|
|
28
|
+
} | undefined> | {
|
|
29
|
+
contents: string;
|
|
30
|
+
loader: 'ts' | 'tsx';
|
|
31
|
+
} | undefined): void;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
34
|
+
interface ActureBuildTierOptions {
|
|
35
|
+
/** File-path regex. Defaults to `/\\.tsx?$/`. */
|
|
36
|
+
filter?: RegExp;
|
|
37
|
+
/** Skip transform for paths matching this regex. Defaults to a
|
|
38
|
+
* `node_modules` matcher. */
|
|
39
|
+
exclude?: RegExp;
|
|
40
|
+
}
|
|
41
|
+
/** esbuild / tsup plugin. */
|
|
42
|
+
declare function actureBuildTier(options?: ActureBuildTierOptions): ESBuildPlugin;
|
|
43
|
+
|
|
44
|
+
export { type ActureBuildTierOptions, type ESBuildPlugin, actureBuildTier };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild plugin. Also works with tsup, which is esbuild under the hood.
|
|
3
|
+
*
|
|
4
|
+
* Usage in a consumer's tsup.config.ts:
|
|
5
|
+
*
|
|
6
|
+
* import { actureBuildTier } from 'acture-build-tier/esbuild';
|
|
7
|
+
* export default defineConfig({
|
|
8
|
+
* entry: ['src/index.ts'],
|
|
9
|
+
* esbuildPlugins: [actureBuildTier()],
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* The plugin matches `.ts` and `.tsx` files inside the consumer's
|
|
13
|
+
* project (it deliberately does NOT touch `node_modules/`).
|
|
14
|
+
*/
|
|
15
|
+
/** Minimal subset of the esbuild Plugin shape we depend on, to avoid
|
|
16
|
+
* pulling esbuild's types into our public surface. */
|
|
17
|
+
interface ESBuildPlugin {
|
|
18
|
+
name: string;
|
|
19
|
+
setup(build: {
|
|
20
|
+
onLoad(options: {
|
|
21
|
+
filter: RegExp;
|
|
22
|
+
namespace?: string;
|
|
23
|
+
}, callback: (args: {
|
|
24
|
+
path: string;
|
|
25
|
+
}) => Promise<{
|
|
26
|
+
contents: string;
|
|
27
|
+
loader: 'ts' | 'tsx';
|
|
28
|
+
} | undefined> | {
|
|
29
|
+
contents: string;
|
|
30
|
+
loader: 'ts' | 'tsx';
|
|
31
|
+
} | undefined): void;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|
|
34
|
+
interface ActureBuildTierOptions {
|
|
35
|
+
/** File-path regex. Defaults to `/\\.tsx?$/`. */
|
|
36
|
+
filter?: RegExp;
|
|
37
|
+
/** Skip transform for paths matching this regex. Defaults to a
|
|
38
|
+
* `node_modules` matcher. */
|
|
39
|
+
exclude?: RegExp;
|
|
40
|
+
}
|
|
41
|
+
/** esbuild / tsup plugin. */
|
|
42
|
+
declare function actureBuildTier(options?: ActureBuildTierOptions): ESBuildPlugin;
|
|
43
|
+
|
|
44
|
+
export { type ActureBuildTierOptions, type ESBuildPlugin, actureBuildTier };
|
package/dist/esbuild.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { transformSource } from './chunk-SWJN6WEL.js';
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
function actureBuildTier(options = {}) {
|
|
5
|
+
const filter = options.filter ?? /\.tsx?$/;
|
|
6
|
+
const exclude = options.exclude ?? /node_modules/;
|
|
7
|
+
return {
|
|
8
|
+
name: "acture-build-tier",
|
|
9
|
+
setup(build) {
|
|
10
|
+
build.onLoad({ filter }, async (args) => {
|
|
11
|
+
if (exclude.test(args.path)) return void 0;
|
|
12
|
+
const buf = await readFile(args.path, "utf8");
|
|
13
|
+
const { code, changed } = transformSource(buf);
|
|
14
|
+
if (!changed) return void 0;
|
|
15
|
+
const loader = args.path.endsWith(".tsx") ? "tsx" : "ts";
|
|
16
|
+
return { contents: code, loader };
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { actureBuildTier };
|
|
23
|
+
//# sourceMappingURL=esbuild.js.map
|
|
24
|
+
//# sourceMappingURL=esbuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/esbuild.ts"],"names":[],"mappings":";;;AA0CO,SAAS,eAAA,CAAgB,OAAA,GAAkC,EAAC,EAAkB;AACnF,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAA,IAAU,SAAA;AACjC,EAAA,MAAM,OAAA,GAAU,QAAQ,OAAA,IAAW,cAAA;AACnC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,mBAAA;AAAA,IACN,MAAM,KAAA,EAAO;AACX,MAAA,KAAA,CAAM,MAAA,CAAO,EAAE,MAAA,EAAO,EAAG,OAAO,IAAA,KAAS;AACvC,QAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,IAAI,GAAG,OAAO,MAAA;AACpC,QAAA,MAAM,GAAA,GAAM,MAAM,QAAA,CAAS,IAAA,CAAK,MAAM,MAAM,CAAA;AAC5C,QAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAQ,GAAI,gBAAgB,GAAG,CAAA;AAC7C,QAAA,IAAI,CAAC,SAAS,OAAO,MAAA;AACrB,QAAA,MAAM,SAAuB,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,MAAM,IAAI,KAAA,GAAQ,IAAA;AAClE,QAAA,OAAO,EAAE,QAAA,EAAU,IAAA,EAAM,MAAA,EAAO;AAAA,MAClC,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF","file":"esbuild.js","sourcesContent":["/**\n * esbuild plugin. Also works with tsup, which is esbuild under the hood.\n *\n * Usage in a consumer's tsup.config.ts:\n *\n * import { actureBuildTier } from 'acture-build-tier/esbuild';\n * export default defineConfig({\n * entry: ['src/index.ts'],\n * esbuildPlugins: [actureBuildTier()],\n * });\n *\n * The plugin matches `.ts` and `.tsx` files inside the consumer's\n * project (it deliberately does NOT touch `node_modules/`).\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { transformSource } from './transform.js';\n\n/** Minimal subset of the esbuild Plugin shape we depend on, to avoid\n * pulling esbuild's types into our public surface. */\nexport interface ESBuildPlugin {\n name: string;\n setup(build: {\n onLoad(\n options: { filter: RegExp; namespace?: string },\n callback: (args: { path: string }) => Promise<\n | { contents: string; loader: 'ts' | 'tsx'; }\n | undefined\n > | { contents: string; loader: 'ts' | 'tsx'; } | undefined,\n ): void;\n }): void;\n}\n\nexport interface ActureBuildTierOptions {\n /** File-path regex. Defaults to `/\\\\.tsx?$/`. */\n filter?: RegExp;\n /** Skip transform for paths matching this regex. Defaults to a\n * `node_modules` matcher. */\n exclude?: RegExp;\n}\n\n/** esbuild / tsup plugin. */\nexport function actureBuildTier(options: ActureBuildTierOptions = {}): ESBuildPlugin {\n const filter = options.filter ?? /\\.tsx?$/;\n const exclude = options.exclude ?? /node_modules/;\n return {\n name: 'acture-build-tier',\n setup(build) {\n build.onLoad({ filter }, async (args) => {\n if (exclude.test(args.path)) return undefined;\n const buf = await readFile(args.path, 'utf8');\n const { code, changed } = transformSource(buf);\n if (!changed) return undefined;\n const loader: 'ts' | 'tsx' = args.path.endsWith('.tsx') ? 'tsx' : 'ts';\n return { contents: code, loader };\n });\n },\n };\n}\n"]}
|