@stacksjs/stx 0.2.171 → 0.2.173

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.
@@ -108,6 +108,19 @@ export declare function splitTopLevelImports(code: string): { imports: string, b
108
108
  * checked against the server block, or do not and they are `any`.
109
109
  */
110
110
  export declare function clientPayloadDeclarations(serverCode: string): string;
111
+ /**
112
+ * Declarations at the TOP LEVEL of a block, ignoring anything nested.
113
+ *
114
+ * `collectBlockDeclarations` matches `^\s*const …` with leading whitespace
115
+ * allowed, so it also returns bindings local to a function. For most callers
116
+ * that is harmless. For the bridge warning it is not: a `const state` inside a
117
+ * helper never crosses to the client, and naming it told the author to declare
118
+ * a payload entry that would do nothing (stacksjs/stx#1908).
119
+ *
120
+ * Nested braces are blanked first, so only depth-zero declarations survive to
121
+ * be matched by the shared patterns.
122
+ */
123
+ export declare function collectTopLevelDeclarations(code: string): string[];
111
124
  /**
112
125
  * Server bindings a client block reaches for without a declared payload.
113
126
  *
@@ -1,18 +1,22 @@
1
1
  import { stripCommentsAndLiterals } from "./strip-literals.js";
2
2
  import { STX_RUNTIME_GLOBALS } from "./runtime-globals.js";
3
3
  const SCRIPT_RE = /<script(\s[^>]*)?>([\s\S]*?)<\/script>/gi;
4
+ function blankCommentContents(source) {
5
+ const blank = (match) => match.replace(/[^\n]/g, " ");
6
+ return source.replace(/<!--[\s\S]*?-->/g, blank).replace(/\{\{--[\s\S]*?--\}\}/g, blank);
7
+ }
4
8
  export function extractScriptBlocks(source) {
5
- const blocks = [];
9
+ const blocks = [], scannable = blankCommentContents(source);
6
10
  SCRIPT_RE.lastIndex = 0;
7
11
  let match;
8
- while ((match = SCRIPT_RE.exec(source)) !== null) {
9
- const attrs = (match[1] || "").trim(), body = match[2] ?? "";
12
+ while ((match = SCRIPT_RE.exec(scannable)) !== null) {
13
+ const attrs = (match[1] || "").trim(), openLength = (match[0].match(/^<script(?:\s[^>]*)?>/i)?.[0] ?? "<script>").length, closeLength = (match[0].match(/<\/script\s*>$/i)?.[0] ?? "</script>").length, bodyStart = match.index + openLength, bodyEnd = match.index + match[0].length - closeLength, body = source.slice(bodyStart, bodyEnd);
10
14
  if (/\bsrc\s*=/.test(attrs))
11
15
  continue;
12
16
  const typeMatch = attrs.match(/\btype\s*=\s*["']([^"']+)["']/i);
13
17
  if (typeMatch && !/^(?:module|text\/(?:javascript|typescript))$/i.test(typeMatch[1]))
14
18
  continue;
15
- const kind = /\bserver\b/.test(attrs) ? "server" : /\bclient\b/.test(attrs) ? "client" : "plain", openTag = match[0].match(/^<script[^>]*>/i)?.[0] ?? "<script>", startLine = lineAt(source, match.index + openTag.length);
19
+ const kind = /\bserver\b/.test(attrs) ? "server" : /\bclient\b/.test(attrs) ? "client" : "plain", startLine = lineAt(source, bodyStart);
16
20
  blocks.push({ kind, code: body, startLine, attrs });
17
21
  }
18
22
  return blocks;
@@ -108,7 +112,9 @@ const NON_EXPRESSION_ATTRS = new Set([
108
112
  "x-teleport",
109
113
  "v-slot",
110
114
  "x-hydrate",
111
- ":key"
115
+ ":key",
116
+ "x-tooltip",
117
+ "x-tooltip-position"
112
118
  ]);
113
119
  export function extractTemplateExpressions(source) {
114
120
  const masked = maskNonTemplateRegions(source), found = [], interpolation = /(@?)\{\{([\s\S]*?)\}\}|(@?)\{!!([\s\S]*?)!!\}/g;
@@ -313,11 +319,34 @@ export function clientPayloadDeclarations(serverCode) {
313
319
  return parts.join(`
314
320
  `);
315
321
  }
322
+ export function collectTopLevelDeclarations(code) {
323
+ const chars = stripCommentsAndLiterals(code).split("");
324
+ let depth = 0;
325
+ for (let i = 0;i < chars.length; i++) {
326
+ const c = chars[i];
327
+ if (c === "{" || c === "(" || c === "[") {
328
+ depth++;
329
+ continue;
330
+ }
331
+ if (c === "}" || c === ")" || c === "]") {
332
+ depth = Math.max(0, depth - 1);
333
+ continue;
334
+ }
335
+ if (depth > 0 && c !== `
336
+ `)
337
+ chars[i] = " ";
338
+ }
339
+ return collectBlockDeclarations(chars.join(""));
340
+ }
316
341
  export function scrapedBridgeNames(serverCode, clientCode) {
317
342
  if (extractClientPayloadNames(serverCode) !== null)
318
343
  return [];
319
- const searchable = stripCommentsAndLiterals(clientCode);
320
- return collectBlockDeclarations(serverCode).filter((name) => {
344
+ const searchable = stripCommentsAndLiterals(clientCode), provided = new Set(STX_RUNTIME_GLOBALS);
345
+ return collectTopLevelDeclarations(serverCode).filter((name) => {
346
+ if (provided.has(name))
347
+ return !1;
348
+ if (collectBlockDeclarations(clientCode).includes(name))
349
+ return !1;
321
350
  return new RegExp(`(?<![.#\\w$])${name}(?![\\w$])`).test(searchable);
322
351
  });
323
352
  }
@@ -29,19 +29,6 @@ export declare function sourcePathFor(virtualPath: string): string;
29
29
  * than hand tsc a path to nothing.
30
30
  */
31
31
  export declare function findRuntimeTypeDeclarations(): string | null;
32
- /**
33
- * Type-check `.stx` files.
34
- *
35
- * Runs the `tsc` BINARY over generated virtual files rather than driving the
36
- * compiler API. TypeScript 7 ships as a Go port whose npm package exposes only
37
- * `version` — no `createProgram`, no `ScriptTarget` — so an API-based checker
38
- * would work on TS 5 and break on the version this repo actually resolves.
39
- * Shelling out works across both.
40
- *
41
- * TypeScript is not a declared dependency: anyone running a type-check already
42
- * has it, and making the package depend on a compiler to serve a page would be
43
- * the wrong trade.
44
- */
45
32
  export declare function typecheckStxFiles(files: string[], options?: TypecheckOptions): Promise<TypecheckResult>;
46
33
  /** Render diagnostics the way a CLI should: file:line:col, then the message. */
47
34
  export declare function formatTypecheckDiagnostics(diagnostics: TypecheckDiagnostic[]): string;
@@ -64,6 +51,7 @@ export declare interface TypecheckOptions {
64
51
  }
65
52
  export declare interface TypecheckResult {
66
53
  diagnostics: TypecheckDiagnostic[]
54
+ failure?: string
67
55
  checkedFiles: string[]
68
56
  blockCount: number
69
57
  expressionCount: number
package/dist/typecheck.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { STX_RUNTIME_GLOBALS } from "./runtime-globals.js";
4
4
  import { stateDir } from "./state-dir.js";
@@ -39,6 +39,89 @@ export function findRuntimeTypeDeclarations() {
39
39
  return null;
40
40
  }
41
41
  const TSC_LINE_RE = /^(.*?)\((\d+),(\d+)\):\s+(error|warning)\s+TS(\d+):\s+(.*)$/;
42
+ function readProjectPathAliases(startDir) {
43
+ let dir = path.resolve(startDir), configPath;
44
+ for (;; ) {
45
+ const candidate = path.join(dir, "tsconfig.json");
46
+ if (existsSync(candidate)) {
47
+ configPath = candidate;
48
+ break;
49
+ }
50
+ const parent = path.dirname(dir);
51
+ if (parent === dir)
52
+ break;
53
+ dir = parent;
54
+ }
55
+ if (!configPath)
56
+ return {};
57
+ const stripJsonc = (text) => {
58
+ let out = "", inString = !1, escaped = !1;
59
+ for (let i = 0;i < text.length; i++) {
60
+ const c = text[i];
61
+ if (inString) {
62
+ out += c;
63
+ if (escaped)
64
+ escaped = !1;
65
+ else if (c === "\\")
66
+ escaped = !0;
67
+ else if (c === '"')
68
+ inString = !1;
69
+ continue;
70
+ }
71
+ if (c === '"') {
72
+ inString = !0;
73
+ out += c;
74
+ continue;
75
+ }
76
+ if (c === "/" && text[i + 1] === "/") {
77
+ while (i < text.length && text[i] !== `
78
+ `)
79
+ i++;
80
+ out += `
81
+ `;
82
+ continue;
83
+ }
84
+ if (c === "/" && text[i + 1] === "*") {
85
+ i += 2;
86
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
87
+ i++;
88
+ i++;
89
+ continue;
90
+ }
91
+ out += c;
92
+ }
93
+ return out.replace(/,(\s*[}\]])/g, "$1");
94
+ }, seen = new Set;
95
+ let current = configPath, paths, baseUrl, owner = configPath;
96
+ while (current && !seen.has(current)) {
97
+ seen.add(current);
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(stripJsonc(readFileSync(current, "utf8")));
101
+ } catch {
102
+ break;
103
+ }
104
+ const co = parsed?.compilerOptions ?? {};
105
+ if (!paths && co.paths && typeof co.paths === "object") {
106
+ paths = co.paths;
107
+ owner = current;
108
+ }
109
+ if (!baseUrl && typeof co.baseUrl === "string")
110
+ baseUrl = path.resolve(path.dirname(current), co.baseUrl);
111
+ if (paths && baseUrl)
112
+ break;
113
+ current = typeof parsed?.extends === "string" ? path.resolve(path.dirname(current), parsed.extends.endsWith(".json") ? parsed.extends : `${parsed.extends}.json`) : void 0;
114
+ }
115
+ if (!paths)
116
+ return {};
117
+ const base = baseUrl ?? path.dirname(owner), absolute = {};
118
+ for (const [pattern, targets] of Object.entries(paths)) {
119
+ if (!Array.isArray(targets))
120
+ continue;
121
+ absolute[pattern] = targets.map((t) => typeof t === "string" && !path.isAbsolute(t) ? path.resolve(base, t) : t);
122
+ }
123
+ return { paths: absolute };
124
+ }
42
125
  export async function typecheckStxFiles(files, options = {}) {
43
126
  const checkClient = options.client !== !1, checkServer = options.server !== !1, checkTemplates = options.templates !== !1, virtualFiles = new Map, scrapedByFile = new Map, checkedFiles = [];
44
127
  let blockCount = 0, expressionCount = 0;
@@ -80,7 +163,7 @@ export async function typecheckStxFiles(files, options = {}) {
80
163
  }
81
164
  if (virtualFiles.size === 0)
82
165
  return { diagnostics: [], checkedFiles, blockCount: 0, expressionCount: 0 };
83
- const ambient = [...options.extraLibs ?? []];
166
+ const ambient = (options.extraLibs ?? []).map((lib) => path.resolve(lib));
84
167
  if (runtimeTypes)
85
168
  ambient.push(runtimeTypes);
86
169
  const globalsDts = `${stateDir()}/typecheck/__stx_globals.d.ts`, globalDecls = [
@@ -112,6 +195,7 @@ export async function typecheckStxFiles(files, options = {}) {
112
195
  resolveJsonModule: !0,
113
196
  lib: ["ESNext", "DOM", "DOM.Iterable"],
114
197
  types: [],
198
+ ...readProjectPathAliases(files.length > 0 ? path.dirname(path.resolve(files[0])) : process.cwd()),
115
199
  ...options.compilerOptions ?? {}
116
200
  },
117
201
  files: [...writtenToOrigin.keys()].map((name) => `${workDir}/${name}`).concat(globalsDts, ...ambient)
@@ -120,12 +204,18 @@ export async function typecheckStxFiles(files, options = {}) {
120
204
  const proc = Bun.spawnSync(["bun", "x", "tsc", "-p", tsconfigPath, "--pretty", "false"], {
121
205
  stdout: "pipe",
122
206
  stderr: "pipe"
123
- }), output = new TextDecoder().decode(proc.stdout) + new TextDecoder().decode(proc.stderr), diagnostics = [];
207
+ }), output = new TextDecoder().decode(proc.stdout) + new TextDecoder().decode(proc.stderr), diagnostics = [], exitCode = proc.exitCode ?? 0;
208
+ let printedDiagnostics = 0;
209
+ const ambientBasenames = new Set(ambient.map((lib) => lib.split("/").pop()));
210
+ let ambientDiagnostics = 0;
124
211
  for (const rawLine of output.split(`
125
212
  `)) {
126
213
  const m = rawLine.match(TSC_LINE_RE);
127
214
  if (!m)
128
215
  continue;
216
+ printedDiagnostics++;
217
+ if (ambientBasenames.has(m[1].split("/").pop()))
218
+ ambientDiagnostics++;
129
219
  const [, filePart, lineStr, colStr, category, code, message] = m, entry = writtenToOrigin.get(filePart.split("/").pop());
130
220
  if (!entry)
131
221
  continue;
@@ -173,7 +263,11 @@ export async function typecheckStxFiles(files, options = {}) {
173
263
  return !0;
174
264
  });
175
265
  unique.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column);
176
- return { diagnostics: unique, checkedFiles, blockCount, expressionCount };
266
+ const brokenLib = ambientDiagnostics > 0 && unique.length === 0, failure = brokenLib || exitCode !== 0 && printedDiagnostics === 0 ? `${brokenLib ? "a file passed with --lib has errors of its own, so nothing in your code was checked" : `tsc exited ${exitCode} without reporting any diagnostic, so nothing was checked`}. ` + "A syntax error in an ambient declaration file does this \u2014 skipLibCheck suppresses " + `SEMANTIC diagnostics only. tsc said:
267
+ ${output.trim().split(`
268
+ `).slice(0, 12).join(`
269
+ `) || "(no output)"}` : void 0;
270
+ return { diagnostics: unique, checkedFiles, blockCount, expressionCount, failure };
177
271
  }
178
272
  export function formatTypecheckDiagnostics(diagnostics) {
179
273
  return diagnostics.map((d) => {
@@ -676,7 +676,7 @@ export function convertToCommonJS(scriptContent, filePath) {
676
676
  };
677
677
  if (defaultImportMatch) {
678
678
  const [, name, source] = defaultImportMatch, resolved = resolveSource(source);
679
- convertedLines.push(`const ${name} = await import('${resolved}')`);
679
+ convertedLines.push(`const ${name} = (await import('${resolved}')).default`);
680
680
  convertedLines.push(`module.exports.${name} = ${name};`);
681
681
  } else if (namedImportMatch) {
682
682
  const [, names, source] = namedImportMatch, resolved = resolveSource(source), specifiers = [], exported = [], isStxRuntime = STX_RUNTIME_SPECIFIERS.has(source);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.171",
4
+ "version": "0.2.173",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -117,12 +117,12 @@
117
117
  "dependencies": {
118
118
  "@cwcss/crosswind": "^0.2.14",
119
119
  "@stacksjs/clapp": "^0.2.0",
120
- "@stacksjs/desktop": "0.2.171",
121
- "@stacksjs/sanitizer": "0.2.171",
120
+ "@stacksjs/desktop": "0.2.173",
121
+ "@stacksjs/sanitizer": "0.2.173",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.171",
123
+ "bun-plugin-stx": "0.2.173",
124
124
  "bunfig": "^0.15.17",
125
- "stx-router": "0.2.171",
125
+ "stx-router": "0.2.173",
126
126
  "ts-broadcasting": "^0.0.9",
127
127
  "ts-images": "^0.1.9",
128
128
  "ts-syntax-highlighter": "^0.2.1"