layero 0.7.1 → 0.7.2

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.
@@ -214,6 +214,7 @@ async function resolveSetupConfig(cwd, opts, existing) {
214
214
  output_dir: detected.output_dir,
215
215
  confident: detected.confident,
216
216
  ...(detected.runtime_kind ? { runtime_kind: detected.runtime_kind } : {}),
217
+ ...(detected.ssr_warning ? { ssr_warning: detected.ssr_warning } : {}),
217
218
  });
218
219
  const framework_hint = opts.type ?? existing?.framework_hint ?? detected.framework_hint;
219
220
  const build_cmd = existing?.build_cmd ?? detected.build_cmd;
package/dist/detect.js CHANGED
@@ -32,6 +32,13 @@ async function fileExists(cwd, ...candidates) {
32
32
  // hit on 2026-05-22 (builder detector v72 fix) and 2026-05-26 (CLI side)
33
33
  // was exactly this kind of dual detector drift.
34
34
  const NEXT_STATIC_EXPORT_RE = /output\s*:\s*['"]export['"]/m;
35
+ // Strip JS line and block comments before regex matching. Without this,
36
+ // a comment like `// see output: 'export'` falsely matches as
37
+ // static-export — observed 2026-05-26 on the SSR smoke canary fixture.
38
+ // Naive (does not understand strings that contain comment-like tokens),
39
+ // but real next.config files don't have that shape.
40
+ const JS_COMMENT_RE = /\/\/[^\n]*|\/\*[\s\S]*?\*\//gm;
41
+ const stripJsComments = (text) => text.replace(JS_COMMENT_RE, "");
35
42
  async function readNextConfigText(cwd) {
36
43
  for (const name of ["next.config.mjs", "next.config.ts", "next.config.js", "next.config.cjs"]) {
37
44
  try {
@@ -52,7 +59,7 @@ async function nextConfigStaticExport(cwd) {
52
59
  const text = await readNextConfigText(cwd);
53
60
  if (text === null)
54
61
  return null;
55
- return NEXT_STATIC_EXPORT_RE.test(text);
62
+ return NEXT_STATIC_EXPORT_RE.test(stripJsComments(text));
56
63
  }
57
64
  async function hasAnyHtml(cwd) {
58
65
  // Shallow check — only top-level. Recursive walk is too expensive here
@@ -112,19 +119,50 @@ export async function detectProject(cwd) {
112
119
  : buildScript
113
120
  ? "npm run build"
114
121
  : "npx nuxt generate";
122
+ // Nuxt defaults to SSR. Layero hosts only static for Nuxt today,
123
+ // so a project without an explicit static signal (`nuxt generate`
124
+ // script, or `ssr: false` / `nitro.preset='static'` in config)
125
+ // will build but the resulting `.output/server/` is useless to us.
126
+ // Mirrors builder's `frameworks/nuxt.py` SSR hint.
127
+ const nuxtStatic = !!generateScript || (await nuxtConfigDeclaresStatic(cwd));
115
128
  return {
116
129
  framework_hint: "nuxt",
117
130
  build_cmd: cmd,
118
131
  output_dir: ".output/public",
119
132
  confident: true,
133
+ ...(nuxtStatic ? {} : {
134
+ ssr_warning: "Nuxt без `nuxt generate` или `ssr: false` собирается как SSR-сервер; " +
135
+ "Layero хостит только статику Nuxt. Добавьте `\"generate\": \"nuxt generate\"` " +
136
+ "в scripts или поставьте `ssr: false` в nuxt.config.",
137
+ }),
120
138
  };
121
139
  }
122
140
  if (hasDep(pkg, "@sveltejs/kit") || (await fileExists(cwd, "svelte.config.js"))) {
141
+ // SvelteKit needs an adapter. adapter-static = SPA path; anything
142
+ // else (adapter-node, adapter-auto, …) is server-side. Mirrors
143
+ // builder's `frameworks/svelte.py:validate_static`.
144
+ const hasStaticAdapter = hasDep(pkg, "@sveltejs/adapter-static");
145
+ const serverAdapter = !hasStaticAdapter
146
+ ? Object.keys({ ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) })
147
+ .find((d) => d.startsWith("@sveltejs/adapter-") && d !== "@sveltejs/adapter-static")
148
+ : undefined;
149
+ let ssrWarning;
150
+ if (serverAdapter) {
151
+ ssrWarning =
152
+ `SvelteKit использует ${serverAdapter} (серверный адаптер). ` +
153
+ "Layero хостит только статику — поставьте `@sveltejs/adapter-static`.";
154
+ }
155
+ else if (!hasStaticAdapter) {
156
+ ssrWarning =
157
+ "SvelteKit без явного адаптера — поставьте `@sveltejs/adapter-static` " +
158
+ "для статического хостинга на Layero.";
159
+ }
123
160
  return {
124
161
  framework_hint: "sveltekit",
125
162
  build_cmd: buildCmd(pkg, "npx vite build"),
126
163
  output_dir: "build",
127
164
  confident: true,
165
+ ...(ssrWarning ? { ssr_warning: ssrWarning } : {}),
128
166
  };
129
167
  }
130
168
  if (hasDep(pkg, "gatsby") || (await fileExists(cwd, "gatsby-config.js", "gatsby-config.ts"))) {
@@ -279,6 +317,27 @@ const HUGO_CONFIG_TOKENS = [
279
317
  "minVersion",
280
318
  "theme",
281
319
  ];
320
+ // Match `ssr: false` or `nitro: { preset: 'static' }` in nuxt.config —
321
+ // either marker keeps the project on the SPA pipeline. Mirrors the same
322
+ // "string-search before AST" approach used for Next.js.
323
+ const NUXT_STATIC_SSR_RE = /\bssr\s*:\s*false\b/m;
324
+ const NUXT_STATIC_PRESET_RE = /preset\s*:\s*['"]static['"]/m;
325
+ async function nuxtConfigDeclaresStatic(cwd) {
326
+ for (const name of ["nuxt.config.mjs", "nuxt.config.ts", "nuxt.config.js"]) {
327
+ try {
328
+ const txt = stripJsComments(await fs.readFile(path.join(cwd, name), "utf-8"));
329
+ if (NUXT_STATIC_SSR_RE.test(txt) || NUXT_STATIC_PRESET_RE.test(txt))
330
+ return true;
331
+ return false; // config found, no static marker → SSR
332
+ }
333
+ catch (err) {
334
+ if (err?.code === "ENOENT")
335
+ continue;
336
+ return false;
337
+ }
338
+ }
339
+ return false; // no config at all → defaults to SSR
340
+ }
282
341
  async function hasVitepressConfig(cwd, prefix = ".vitepress") {
283
342
  for (const name of ["config.ts", "config.js", "config.mts", "config.mjs"]) {
284
343
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "layero",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Layero CLI — publish a local site with one command. No git, no GitHub, agent-friendly (Cursor, Claude Code).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,6 +41,7 @@
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.json",
43
43
  "prepublishOnly": "npm run build",
44
+ "test": "vitest run",
44
45
  "dev": "tsc -p tsconfig.json --watch",
45
46
  "postinstall": "node scripts/postinstall.cjs || true"
46
47
  },
@@ -53,6 +54,7 @@
53
54
  },
54
55
  "devDependencies": {
55
56
  "@types/node": "^20.14.10",
56
- "typescript": "^5.5.4"
57
+ "typescript": "^5.5.4",
58
+ "vitest": "^4.1.7"
57
59
  }
58
60
  }