@analogjs/platform 3.0.0-alpha.24 → 3.0.0-alpha.26

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.
Files changed (43) hide show
  1. package/package.json +15 -3
  2. package/src/index.d.ts +4 -0
  3. package/src/index.js +3 -1
  4. package/src/index.js.map +1 -1
  5. package/src/lib/content-plugin.js +5 -0
  6. package/src/lib/content-plugin.js.map +1 -1
  7. package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js +4 -1
  8. package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js.map +1 -1
  9. package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.d.ts +1 -0
  10. package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js +13 -0
  11. package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js.map +1 -1
  12. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +7 -5
  13. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js +4 -0
  14. package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
  15. package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.d.ts +1 -1
  16. package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.js +2 -0
  17. package/src/lib/nx-plugin/src/generators/app/versions/tailwind-dependencies.js.map +1 -1
  18. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
  19. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
  20. package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
  21. package/src/lib/options.d.ts +18 -2
  22. package/src/lib/platform-plugin.js +6 -1
  23. package/src/lib/platform-plugin.js.map +1 -1
  24. package/src/lib/route-idiom-diagnostics.d.ts +13 -0
  25. package/src/lib/route-idiom-diagnostics.js +160 -0
  26. package/src/lib/route-idiom-diagnostics.js.map +1 -0
  27. package/src/lib/router-plugin.js +41 -0
  28. package/src/lib/router-plugin.js.map +1 -1
  29. package/src/lib/style-pipeline.d.ts +34 -0
  30. package/src/lib/style-pipeline.js +32 -0
  31. package/src/lib/style-pipeline.js.map +1 -0
  32. package/src/lib/style-preprocessor.d.ts +28 -0
  33. package/src/lib/style-preprocessor.js +35 -0
  34. package/src/lib/style-preprocessor.js.map +1 -0
  35. package/src/lib/tailwind-preprocessor.d.ts +1 -1
  36. package/src/lib/tailwind-preprocessor.js.map +1 -1
  37. package/src/lib/utils/debug.d.ts +2 -1
  38. package/src/lib/utils/debug.js +2 -1
  39. package/src/lib/utils/debug.js.map +1 -1
  40. package/src/style-pipeline.d.ts +1 -0
  41. package/src/style-pipeline.js +2 -0
  42. package/src/style-preprocessor.d.ts +1 -0
  43. package/src/style-preprocessor.js +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@analogjs/platform",
3
- "version": "3.0.0-alpha.24",
3
+ "version": "3.0.0-alpha.26",
4
4
  "description": "The fullstack meta-framework for Angular",
5
5
  "type": "module",
6
6
  "author": "Brandon Roberts <robertsbt@gmail.com>",
@@ -9,6 +9,18 @@
9
9
  "types": "./src/index.d.ts",
10
10
  "default": "./src/index.js"
11
11
  },
12
+ "./style-pipeline": {
13
+ "types": "./src/style-pipeline.d.ts",
14
+ "import": "./src/style-pipeline.js",
15
+ "require": "./src/style-pipeline.js",
16
+ "default": "./src/style-pipeline.js"
17
+ },
18
+ "./style-preprocessor": {
19
+ "types": "./src/style-preprocessor.d.ts",
20
+ "import": "./src/style-preprocessor.js",
21
+ "require": "./src/style-preprocessor.js",
22
+ "default": "./src/style-preprocessor.js"
23
+ },
12
24
  "./package.json": "./package.json"
13
25
  },
14
26
  "keywords": [
@@ -35,8 +47,8 @@
35
47
  "es-toolkit": "^1.45.1",
36
48
  "tinyglobby": "^0.2.15",
37
49
  "nitro": "3.0.260311-beta",
38
- "@analogjs/vite-plugin-angular": "3.0.0-alpha.24",
39
- "@analogjs/vite-plugin-nitro": "3.0.0-alpha.24",
50
+ "@analogjs/vite-plugin-angular": "3.0.0-alpha.26",
51
+ "@analogjs/vite-plugin-nitro": "3.0.0-alpha.26",
40
52
  "rolldown": "^1.0.0-rc.13",
41
53
  "obug": "^2.1.1",
42
54
  "vitefu": "^1.1.2"
package/src/index.d.ts CHANGED
@@ -2,5 +2,9 @@ import { platformPlugin } from "./lib/platform-plugin.js";
2
2
  export type { Options, PrerenderSitemapConfig, TypedRouterOptions, PrerenderContentFile, SitemapConfig, SitemapEntry, SitemapExcludeRule, SitemapPriority, SitemapRouteDefinition, SitemapRouteInput, SitemapRouteSource, SitemapTransform } from "./lib/options.js";
3
3
  export { routeGenerationPlugin } from "./lib/route-generation-plugin.js";
4
4
  export { tailwindPreprocessor } from "./lib/tailwind-preprocessor.js";
5
+ export type { AngularStylePipelineContext, AngularStylePipelineOptions, AngularStylePipelinePlugin, StylePipelineContext, StylePipelineOptions, StylePipelinePluginEntry, StylePipelinePluginFactory } from "./lib/style-pipeline.js";
6
+ export { defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, resolveStylePipelinePlugins } from "./lib/style-pipeline.js";
7
+ export type { StylePreprocessor, StylesheetDependency, StylesheetDiagnostic, StylesheetTransformContext, StylesheetTransformResult } from "./lib/style-preprocessor.js";
8
+ export { composeStylePreprocessors, normalizeStylesheetDependencies, normalizeStylesheetTransformResult } from "./lib/style-preprocessor.js";
5
9
  export type { TailwindPreprocessorMode, TailwindPreprocessorOptions } from "./lib/tailwind-preprocessor.js";
6
10
  export default platformPlugin;
package/src/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { routeGenerationPlugin } from "./lib/route-generation-plugin.js";
2
+ import { defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, resolveStylePipelinePlugins } from "./lib/style-pipeline.js";
2
3
  import { platformPlugin } from "./lib/platform-plugin.js";
3
4
  import { tailwindPreprocessor } from "./lib/tailwind-preprocessor.js";
5
+ import { composeStylePreprocessors, normalizeStylesheetDependencies, normalizeStylesheetTransformResult } from "./lib/style-preprocessor.js";
4
6
  //#region packages/platform/src/index.ts
5
7
  var src_default = platformPlugin;
6
8
  //#endregion
7
- export { src_default as default, routeGenerationPlugin, tailwindPreprocessor };
9
+ export { composeStylePreprocessors, src_default as default, defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, normalizeStylesheetDependencies, normalizeStylesheetTransformResult, resolveStylePipelinePlugins, routeGenerationPlugin, tailwindPreprocessor };
8
10
 
9
11
  //# sourceMappingURL=index.js.map
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { platformPlugin } from './lib/platform-plugin.js';\n\nexport type {\n Options,\n PrerenderSitemapConfig,\n TypedRouterOptions,\n PrerenderContentFile,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapPriority,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n SitemapTransform,\n} from './lib/options.js';\nexport { routeGenerationPlugin } from './lib/route-generation-plugin.js';\nexport { tailwindPreprocessor } from './lib/tailwind-preprocessor.js';\nexport type {\n TailwindPreprocessorMode,\n TailwindPreprocessorOptions,\n} from './lib/tailwind-preprocessor.js';\nexport default platformPlugin;\n"],"mappings":";;;;AAsBA,IAAA,cAAe"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { platformPlugin } from './lib/platform-plugin.js';\n\nexport type {\n Options,\n PrerenderSitemapConfig,\n TypedRouterOptions,\n PrerenderContentFile,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapPriority,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n SitemapTransform,\n} from './lib/options.js';\nexport { routeGenerationPlugin } from './lib/route-generation-plugin.js';\nexport { tailwindPreprocessor } from './lib/tailwind-preprocessor.js';\nexport type {\n AngularStylePipelineContext,\n AngularStylePipelineOptions,\n AngularStylePipelinePlugin,\n StylePipelineContext,\n StylePipelineOptions,\n StylePipelinePluginEntry,\n StylePipelinePluginFactory,\n} from './lib/style-pipeline.js';\nexport {\n defineAngularStylePipeline,\n defineAngularStylePipelinePlugins,\n defineStylePipeline,\n defineStylePipelinePlugins,\n resolveStylePipelinePlugins,\n} from './lib/style-pipeline.js';\nexport type {\n StylePreprocessor,\n StylesheetDependency,\n StylesheetDiagnostic,\n StylesheetTransformContext,\n StylesheetTransformResult,\n} from './lib/style-preprocessor.js';\nexport {\n composeStylePreprocessors,\n normalizeStylesheetDependencies,\n normalizeStylesheetTransformResult,\n} from './lib/style-preprocessor.js';\nexport type {\n TailwindPreprocessorMode,\n TailwindPreprocessorOptions,\n} from './lib/tailwind-preprocessor.js';\nexport default platformPlugin;\n"],"mappings":";;;;;;AAkDA,IAAA,cAAe"}
@@ -84,6 +84,11 @@ function contentPlugin({ highlighter, markedOptions, shikiOptions, prismOptions
84
84
  }
85
85
  });
86
86
  });
87
+ server.ws.send("analog:debug-full-reload", {
88
+ plugin: "platform:content-plugin",
89
+ reason: "content-file-set-changed",
90
+ path: normalizedPath
91
+ });
87
92
  server.ws.send({ type: "full-reload" });
88
93
  }
89
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"content-plugin.js","names":[],"sources":["../../../src/lib/content-plugin.ts"],"sourcesContent":["import * as vite from 'vite';\nimport { readFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { WithShikiHighlighterOptions } from './content/shiki/options.js';\nimport type { MarkedContentHighlighter } from './content/marked/marked-content-highlighter.js';\nimport type { WithPrismHighlighterOptions } from './content/prism/options.js';\nimport type { WithMarkedOptions } from './content/marked/index.js';\nimport type { Options } from './options.js';\nimport { getBundleOptionsKey } from './utils/rolldown.js';\n\ninterface Content {\n code: string;\n attributes: string;\n}\n\nexport type ContentPluginOptions = {\n highlighter?: 'shiki' | 'prism';\n markedOptions?: WithMarkedOptions;\n shikiOptions?: WithShikiHighlighterOptions;\n prismOptions?: WithPrismHighlighterOptions;\n};\n\n/**\n * Content plugin that provides markdown and content file processing for Analog.\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key pitfall with { dot: true }:\n * - Returns relative paths from cwd (e.g., \"apps/blog-app/src/content/...\")\n * - These paths CANNOT be used directly in ES module imports\n * - Relative paths without ./ or ../ are treated as package names\n * - Must convert to absolute paths for imports to work correctly\n */\nexport function contentPlugin(\n {\n highlighter,\n markedOptions,\n shikiOptions,\n prismOptions,\n }: ContentPluginOptions = {},\n options?: Options,\n): vite.Plugin[] {\n const cache = new Map<string, Content>();\n // The content list placeholder can be transformed several times during serve.\n // Caching the discovered markdown paths keeps those repeat passes cheap.\n let contentFilesListCache: string[] | undefined;\n\n let markedHighlighter: MarkedContentHighlighter;\n const workspaceRoot = vite.normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: vite.UserConfig;\n let root: string;\n // Keep discovery and invalidation aligned by deriving both from the same\n // normalized content roots. That way external content dirs participate in\n // cache busting exactly the same way they participate in glob discovery.\n // Initialized once in the `config` hook after `root` is resolved — all\n // inputs (`root`, `workspaceRoot`, `options`) are stable after that point.\n let contentRootDirs: string[];\n const normalizeContentDir = (dir: string) => {\n const normalized = vite.normalizePath(\n dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n };\n const initContentRootDirs = () => {\n contentRootDirs = [\n vite.normalizePath(`${root}/src/content`),\n ...(options?.additionalContentDirs || []).map(normalizeContentDir),\n ];\n };\n const discoverContentFilesList = () => {\n contentFilesListCache ??= globSync(\n contentRootDirs.map((dir) => `${dir}/**/*.md`),\n { dot: true },\n );\n\n return contentFilesListCache;\n };\n const resolveContentModulePath = (module: string) =>\n vite.normalizePath(\n module.startsWith('/') ? module : `${workspaceRoot}/${module}`,\n );\n const getContentModuleKey = (module: string) => {\n const absolutePath = resolveContentModulePath(module);\n const relativeToRoot = vite.normalizePath(relative(root, absolutePath));\n // `startsWith(root)` is not safe here because sibling directories such as\n // `/apps/my-app-tools` also start with `/apps/my-app`. A relative path only\n // represents in-app content when it stays within `root`.\n const isInApp =\n !relativeToRoot.startsWith('..') && !relativeToRoot.startsWith('/');\n\n // Both branches prepend `/` so generated keys are always absolute-\n // looking (`/src/content/...` for in-app, `/libs/shared/...` for\n // workspace-external). Downstream consumers like content-files-token\n // rely on the leading `/` for slug extraction regexes.\n return isInApp\n ? `/${relativeToRoot}`\n : `/${vite.normalizePath(relative(workspaceRoot, absolutePath))}`;\n };\n\n const contentDiscoveryPlugins: vite.Plugin[] = [\n {\n name: 'analog-content-glob-routes',\n config(_config) {\n config = _config;\n root = vite.normalizePath(\n resolve(workspaceRoot, config.root || '.') || '.',\n );\n initContentRootDirs();\n },\n // Vite 8 / Rolldown \"filtered transform\" — the `filter.code` string\n // tells the bundler to skip this handler entirely for modules whose\n // source does not contain the substring, avoiding unnecessary JS→Rust\n // round-trips. The inner `code.includes()` guard is kept for Vite 7\n // compat where filters are not evaluated by the bundler.\n transform: {\n filter: {\n code: 'ANALOG_CONTENT_FILE_LIST',\n },\n handler(code) {\n if (code.includes('ANALOG_CONTENT_FILE_LIST')) {\n const contentFilesList = discoverContentFilesList();\n\n const eagerImports: string[] = [];\n\n contentFilesList.forEach((module, index) => {\n // CRITICAL: tinyglobby returns relative paths like \"apps/blog-app/src/content/file.md\"\n // These MUST be converted to absolute paths for ES module imports\n // Otherwise Node.js treats \"apps\" as a package name and throws \"Cannot find package 'apps'\"\n const absolutePath = resolveContentModulePath(module);\n eagerImports.push(\n `import { default as analog_module_${index} } from \"${absolutePath}?analog-content-list=true\";`,\n );\n });\n\n let result = code.replace(\n 'const ANALOG_CONTENT_FILE_LIST = {};',\n `\n let ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(\n (module, index) =>\n `\"${getContentModuleKey(module)}\": analog_module_${index}`,\n )}};\n `,\n );\n\n if (!code.includes('analog_module_')) {\n result = `${eagerImports.join('\\n')}\\n${result}`;\n }\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analogjs-invalidate-content-dirs',\n configureServer(server) {\n function invalidateContent(path: string) {\n const normalizedPath = vite.normalizePath(path);\n const isContentPath = contentRootDirs.some(\n (dir) =>\n normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n\n if (isContentPath) {\n // The file set only changes on add/remove because this watcher is\n // intentionally scoped to those events. Clear the list cache so the\n // next transform sees the updated directory contents.\n contentFilesListCache = undefined;\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (\n mod.id?.includes('analogjs') &&\n mod.id?.includes('content')\n ) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n }\n\n server.watcher.on('add', invalidateContent);\n server.watcher.on('unlink', invalidateContent);\n },\n },\n ];\n\n if (!highlighter) {\n return [\n {\n name: 'analogjs-external-content',\n config() {\n const bundleOptionsKey = getBundleOptionsKey();\n return {\n build: {\n [bundleOptionsKey]: {\n external: ['@analogjs/content'],\n },\n },\n };\n },\n },\n {\n name: 'analogjs-exclude-content-import',\n transform: {\n filter: {\n code: '@analogjs/content',\n },\n handler(code) {\n /**\n * Remove the package so it doesn't get\n * referenced when building for serverless\n * functions.\n */\n if (code.includes(`import('@analogjs/content')`)) {\n return {\n code: code.replace(\n `import('@analogjs/content')`,\n 'Promise.resolve({})',\n ),\n };\n }\n\n return;\n },\n },\n },\n ...contentDiscoveryPlugins,\n ];\n }\n\n return [\n {\n name: 'analogjs-content-frontmatter',\n // Filter by module ID so only `?analog-content-list=true` virtual\n // imports enter the handler. Returns `moduleSideEffects: false` so\n // Rolldown can tree-shake unused content list entries.\n transform: {\n filter: {\n id: /analog-content-list=true/,\n },\n async handler(code, id) {\n const cachedContent = cache.get(id);\n // There's no reason to run `readFileSync` and frontmatter parsing if the\n // `transform` hook is called with the same code. In such cases, we can simply\n // return the cached attributes, which is faster than repeatedly reading files\n // synchronously during the build process.\n if (cachedContent?.code === code) {\n return {\n code: `export default ${cachedContent.attributes}`,\n moduleSideEffects: false,\n };\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatter = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { attributes } = frontmatter(fileContents);\n const content = {\n code,\n attributes: JSON.stringify(attributes),\n };\n cache.set(id, content);\n\n return {\n code: `export default ${content.attributes}`,\n moduleSideEffects: false,\n };\n },\n },\n },\n {\n name: 'analogjs-content-file',\n enforce: 'post',\n async load(id) {\n if (!id.includes('analog-content-file=true')) {\n return;\n }\n\n if (!markedHighlighter) {\n if (highlighter === 'shiki') {\n const { getShikiHighlighter } =\n await import('./content/shiki/index.js');\n markedHighlighter = getShikiHighlighter(shikiOptions);\n } else {\n const { getPrismHighlighter } =\n await import('./content/prism/index.js');\n markedHighlighter = getPrismHighlighter();\n\n const langs = [\n 'bash',\n 'css',\n 'javascript',\n 'json',\n 'markup',\n 'typescript',\n ];\n\n if (\n Array.isArray(prismOptions?.additionalLangs) &&\n prismOptions?.additionalLangs?.length > 0\n ) {\n langs.push(...prismOptions.additionalLangs);\n }\n\n const loadLanguages = await import('prismjs/components/index.js');\n\n (\n loadLanguages as unknown as { default: (...args: any[]) => any }\n ).default(langs);\n }\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatterFn = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { body, frontmatter } = frontmatterFn(fileContents);\n\n // parse markdown and highlight\n const { getMarkedSetup } = await import('./content/marked/index.js');\n const markedSetupService = getMarkedSetup(\n { mangle: true, ...(markedOptions || {}) },\n markedHighlighter,\n );\n const mdContent = (await markedSetupService\n .getMarkedInstance()\n .parse(body)) as unknown as string;\n\n return `export default ${JSON.stringify(\n `---\\n${frontmatter}\\n---\\n\\n${mdContent}`,\n )}`;\n },\n },\n ...contentDiscoveryPlugins,\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"content-plugin.js","names":[],"sources":["../../../src/lib/content-plugin.ts"],"sourcesContent":["import * as vite from 'vite';\nimport { readFileSync } from 'node:fs';\nimport { relative, resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { WithShikiHighlighterOptions } from './content/shiki/options.js';\nimport type { MarkedContentHighlighter } from './content/marked/marked-content-highlighter.js';\nimport type { WithPrismHighlighterOptions } from './content/prism/options.js';\nimport type { WithMarkedOptions } from './content/marked/index.js';\nimport type { Options } from './options.js';\nimport { getBundleOptionsKey } from './utils/rolldown.js';\n\ninterface Content {\n code: string;\n attributes: string;\n}\n\nexport type ContentPluginOptions = {\n highlighter?: 'shiki' | 'prism';\n markedOptions?: WithMarkedOptions;\n shikiOptions?: WithShikiHighlighterOptions;\n prismOptions?: WithPrismHighlighterOptions;\n};\n\n/**\n * Content plugin that provides markdown and content file processing for Analog.\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key pitfall with { dot: true }:\n * - Returns relative paths from cwd (e.g., \"apps/blog-app/src/content/...\")\n * - These paths CANNOT be used directly in ES module imports\n * - Relative paths without ./ or ../ are treated as package names\n * - Must convert to absolute paths for imports to work correctly\n */\nexport function contentPlugin(\n {\n highlighter,\n markedOptions,\n shikiOptions,\n prismOptions,\n }: ContentPluginOptions = {},\n options?: Options,\n): vite.Plugin[] {\n const cache = new Map<string, Content>();\n // The content list placeholder can be transformed several times during serve.\n // Caching the discovered markdown paths keeps those repeat passes cheap.\n let contentFilesListCache: string[] | undefined;\n\n let markedHighlighter: MarkedContentHighlighter;\n const workspaceRoot = vite.normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: vite.UserConfig;\n let root: string;\n // Keep discovery and invalidation aligned by deriving both from the same\n // normalized content roots. That way external content dirs participate in\n // cache busting exactly the same way they participate in glob discovery.\n // Initialized once in the `config` hook after `root` is resolved — all\n // inputs (`root`, `workspaceRoot`, `options`) are stable after that point.\n let contentRootDirs: string[];\n const normalizeContentDir = (dir: string) => {\n const normalized = vite.normalizePath(\n dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;\n };\n const initContentRootDirs = () => {\n contentRootDirs = [\n vite.normalizePath(`${root}/src/content`),\n ...(options?.additionalContentDirs || []).map(normalizeContentDir),\n ];\n };\n const discoverContentFilesList = () => {\n contentFilesListCache ??= globSync(\n contentRootDirs.map((dir) => `${dir}/**/*.md`),\n { dot: true },\n );\n\n return contentFilesListCache;\n };\n const resolveContentModulePath = (module: string) =>\n vite.normalizePath(\n module.startsWith('/') ? module : `${workspaceRoot}/${module}`,\n );\n const getContentModuleKey = (module: string) => {\n const absolutePath = resolveContentModulePath(module);\n const relativeToRoot = vite.normalizePath(relative(root, absolutePath));\n // `startsWith(root)` is not safe here because sibling directories such as\n // `/apps/my-app-tools` also start with `/apps/my-app`. A relative path only\n // represents in-app content when it stays within `root`.\n const isInApp =\n !relativeToRoot.startsWith('..') && !relativeToRoot.startsWith('/');\n\n // Both branches prepend `/` so generated keys are always absolute-\n // looking (`/src/content/...` for in-app, `/libs/shared/...` for\n // workspace-external). Downstream consumers like content-files-token\n // rely on the leading `/` for slug extraction regexes.\n return isInApp\n ? `/${relativeToRoot}`\n : `/${vite.normalizePath(relative(workspaceRoot, absolutePath))}`;\n };\n\n const contentDiscoveryPlugins: vite.Plugin[] = [\n {\n name: 'analog-content-glob-routes',\n config(_config) {\n config = _config;\n root = vite.normalizePath(\n resolve(workspaceRoot, config.root || '.') || '.',\n );\n initContentRootDirs();\n },\n // Vite 8 / Rolldown \"filtered transform\" — the `filter.code` string\n // tells the bundler to skip this handler entirely for modules whose\n // source does not contain the substring, avoiding unnecessary JS→Rust\n // round-trips. The inner `code.includes()` guard is kept for Vite 7\n // compat where filters are not evaluated by the bundler.\n transform: {\n filter: {\n code: 'ANALOG_CONTENT_FILE_LIST',\n },\n handler(code) {\n if (code.includes('ANALOG_CONTENT_FILE_LIST')) {\n const contentFilesList = discoverContentFilesList();\n\n const eagerImports: string[] = [];\n\n contentFilesList.forEach((module, index) => {\n // CRITICAL: tinyglobby returns relative paths like \"apps/blog-app/src/content/file.md\"\n // These MUST be converted to absolute paths for ES module imports\n // Otherwise Node.js treats \"apps\" as a package name and throws \"Cannot find package 'apps'\"\n const absolutePath = resolveContentModulePath(module);\n eagerImports.push(\n `import { default as analog_module_${index} } from \"${absolutePath}?analog-content-list=true\";`,\n );\n });\n\n let result = code.replace(\n 'const ANALOG_CONTENT_FILE_LIST = {};',\n `\n let ANALOG_CONTENT_FILE_LIST = {${contentFilesList.map(\n (module, index) =>\n `\"${getContentModuleKey(module)}\": analog_module_${index}`,\n )}};\n `,\n );\n\n if (!code.includes('analog_module_')) {\n result = `${eagerImports.join('\\n')}\\n${result}`;\n }\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analogjs-invalidate-content-dirs',\n configureServer(server) {\n function invalidateContent(path: string) {\n const normalizedPath = vite.normalizePath(path);\n const isContentPath = contentRootDirs.some(\n (dir) =>\n normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n\n if (isContentPath) {\n // The file set only changes on add/remove because this watcher is\n // intentionally scoped to those events. Clear the list cache so the\n // next transform sees the updated directory contents.\n contentFilesListCache = undefined;\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (\n mod.id?.includes('analogjs') &&\n mod.id?.includes('content')\n ) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send('analog:debug-full-reload', {\n plugin: 'platform:content-plugin',\n reason: 'content-file-set-changed',\n path: normalizedPath,\n });\n server.ws.send({\n type: 'full-reload',\n });\n }\n }\n\n server.watcher.on('add', invalidateContent);\n server.watcher.on('unlink', invalidateContent);\n },\n },\n ];\n\n if (!highlighter) {\n return [\n {\n name: 'analogjs-external-content',\n config() {\n const bundleOptionsKey = getBundleOptionsKey();\n return {\n build: {\n [bundleOptionsKey]: {\n external: ['@analogjs/content'],\n },\n },\n };\n },\n },\n {\n name: 'analogjs-exclude-content-import',\n transform: {\n filter: {\n code: '@analogjs/content',\n },\n handler(code) {\n /**\n * Remove the package so it doesn't get\n * referenced when building for serverless\n * functions.\n */\n if (code.includes(`import('@analogjs/content')`)) {\n return {\n code: code.replace(\n `import('@analogjs/content')`,\n 'Promise.resolve({})',\n ),\n };\n }\n\n return;\n },\n },\n },\n ...contentDiscoveryPlugins,\n ];\n }\n\n return [\n {\n name: 'analogjs-content-frontmatter',\n // Filter by module ID so only `?analog-content-list=true` virtual\n // imports enter the handler. Returns `moduleSideEffects: false` so\n // Rolldown can tree-shake unused content list entries.\n transform: {\n filter: {\n id: /analog-content-list=true/,\n },\n async handler(code, id) {\n const cachedContent = cache.get(id);\n // There's no reason to run `readFileSync` and frontmatter parsing if the\n // `transform` hook is called with the same code. In such cases, we can simply\n // return the cached attributes, which is faster than repeatedly reading files\n // synchronously during the build process.\n if (cachedContent?.code === code) {\n return {\n code: `export default ${cachedContent.attributes}`,\n moduleSideEffects: false,\n };\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatter = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { attributes } = frontmatter(fileContents);\n const content = {\n code,\n attributes: JSON.stringify(attributes),\n };\n cache.set(id, content);\n\n return {\n code: `export default ${content.attributes}`,\n moduleSideEffects: false,\n };\n },\n },\n },\n {\n name: 'analogjs-content-file',\n enforce: 'post',\n async load(id) {\n if (!id.includes('analog-content-file=true')) {\n return;\n }\n\n if (!markedHighlighter) {\n if (highlighter === 'shiki') {\n const { getShikiHighlighter } =\n await import('./content/shiki/index.js');\n markedHighlighter = getShikiHighlighter(shikiOptions);\n } else {\n const { getPrismHighlighter } =\n await import('./content/prism/index.js');\n markedHighlighter = getPrismHighlighter();\n\n const langs = [\n 'bash',\n 'css',\n 'javascript',\n 'json',\n 'markup',\n 'typescript',\n ];\n\n if (\n Array.isArray(prismOptions?.additionalLangs) &&\n prismOptions?.additionalLangs?.length > 0\n ) {\n langs.push(...prismOptions.additionalLangs);\n }\n\n const loadLanguages = await import('prismjs/components/index.js');\n\n (\n loadLanguages as unknown as { default: (...args: any[]) => any }\n ).default(langs);\n }\n }\n\n const fm: any = await import('front-matter');\n // The `default` property will be available in CommonJS environment, for instance,\n // when running unit tests. It's safe to retrieve `default` first, since we still\n // fallback to the original implementation.\n const frontmatterFn = fm.default || fm;\n const fileContents = readFileSync(id.split('?')[0], 'utf8');\n const { body, frontmatter } = frontmatterFn(fileContents);\n\n // parse markdown and highlight\n const { getMarkedSetup } = await import('./content/marked/index.js');\n const markedSetupService = getMarkedSetup(\n { mangle: true, ...(markedOptions || {}) },\n markedHighlighter,\n );\n const mdContent = (await markedSetupService\n .getMarkedInstance()\n .parse(body)) as unknown as string;\n\n return `export default ${JSON.stringify(\n `---\\n${frontmatter}\\n---\\n\\n${mdContent}`,\n )}`;\n },\n },\n ...contentDiscoveryPlugins,\n ];\n}\n"],"mappings":"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"}
@@ -10,7 +10,10 @@ async function setupTailwindGenerator(tree, rawOptions) {
10
10
  require_add_tailwind_helpers.detectTailwindInstalledVersion(tree);
11
11
  let installTask = () => {};
12
12
  if (!options.skipPackageJson) installTask = require_add_tailwind_helpers.addTailwindRequiredPackages(tree);
13
- if (project.projectType === "application") require_add_tailwind_helpers.updateApplicationStyles(tree, options, project);
13
+ if (project.projectType === "application") {
14
+ require_add_tailwind_helpers.updateApplicationStyles(tree, options, project);
15
+ require_add_tailwind_helpers.writeTailwindPostcssConfig(tree, project);
16
+ }
14
17
  if (!options.skipFormat) await (0, _nx_devkit.formatFiles)(tree);
15
18
  return installTask;
16
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"add-tailwind-config.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/lib/add-tailwind-config.ts"],"sourcesContent":["import {\n formatFiles,\n GeneratorCallback,\n readProjectConfiguration,\n Tree,\n} from '@nx/devkit';\nimport {\n addTailwindRequiredPackages,\n detectTailwindInstalledVersion,\n normalizeOptions,\n updateApplicationStyles,\n} from './add-tailwind-helpers';\n\nexport async function addTailwindConfig(\n tree: Tree,\n projectName: string,\n): Promise<void> {\n await setupTailwindGenerator(tree, {\n project: projectName,\n });\n}\n\nexport interface GeneratorOptions {\n project: string;\n buildTarget?: string;\n skipFormat?: boolean;\n stylesEntryPoint?: string;\n skipPackageJson?: boolean;\n}\n\nexport interface NormalizedGeneratorOptions extends GeneratorOptions {\n buildTarget: string;\n}\n\nexport async function setupTailwindGenerator(\n tree: Tree,\n rawOptions: GeneratorOptions,\n): Promise<GeneratorCallback> {\n const options = normalizeOptions(rawOptions);\n const project = readProjectConfiguration(tree, options.project);\n\n // TODO: use return value for v5+ branching when needed\n detectTailwindInstalledVersion(tree);\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n let installTask: GeneratorCallback = () => {};\n if (!options.skipPackageJson) {\n installTask = addTailwindRequiredPackages(tree);\n }\n\n if (project.projectType === 'application') {\n updateApplicationStyles(tree, options, project);\n }\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return installTask;\n}\n"],"mappings":";;;AAaA,eAAsB,kBACpB,MACA,aACe;AACf,OAAM,uBAAuB,MAAM,EACjC,SAAS,aACV,CAAC;;AAeJ,eAAsB,uBACpB,MACA,YAC4B;CAC5B,MAAM,UAAU,6BAAA,iBAAiB,WAAW;CAC5C,MAAM,WAAA,GAAA,WAAA,0BAAmC,MAAM,QAAQ,QAAQ;AAG/D,8BAAA,+BAA+B,KAAK;CAGpC,IAAI,oBAAuC;AAC3C,KAAI,CAAC,QAAQ,gBACX,eAAc,6BAAA,4BAA4B,KAAK;AAGjD,KAAI,QAAQ,gBAAgB,cAC1B,8BAAA,wBAAwB,MAAM,SAAS,QAAQ;AAGjD,KAAI,CAAC,QAAQ,WACX,QAAA,GAAA,WAAA,aAAkB,KAAK;AAGzB,QAAO"}
1
+ {"version":3,"file":"add-tailwind-config.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/lib/add-tailwind-config.ts"],"sourcesContent":["import {\n formatFiles,\n GeneratorCallback,\n readProjectConfiguration,\n Tree,\n} from '@nx/devkit';\nimport {\n addTailwindRequiredPackages,\n detectTailwindInstalledVersion,\n normalizeOptions,\n updateApplicationStyles,\n writeTailwindPostcssConfig,\n} from './add-tailwind-helpers';\n\nexport async function addTailwindConfig(\n tree: Tree,\n projectName: string,\n): Promise<void> {\n await setupTailwindGenerator(tree, {\n project: projectName,\n });\n}\n\nexport interface GeneratorOptions {\n project: string;\n buildTarget?: string;\n skipFormat?: boolean;\n stylesEntryPoint?: string;\n skipPackageJson?: boolean;\n}\n\nexport interface NormalizedGeneratorOptions extends GeneratorOptions {\n buildTarget: string;\n}\n\nexport async function setupTailwindGenerator(\n tree: Tree,\n rawOptions: GeneratorOptions,\n): Promise<GeneratorCallback> {\n const options = normalizeOptions(rawOptions);\n const project = readProjectConfiguration(tree, options.project);\n\n // TODO: use return value for v5+ branching when needed\n detectTailwindInstalledVersion(tree);\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n let installTask: GeneratorCallback = () => {};\n if (!options.skipPackageJson) {\n installTask = addTailwindRequiredPackages(tree);\n }\n\n if (project.projectType === 'application') {\n updateApplicationStyles(tree, options, project);\n writeTailwindPostcssConfig(tree, project);\n }\n\n if (!options.skipFormat) {\n await formatFiles(tree);\n }\n\n return installTask;\n}\n"],"mappings":";;;AAcA,eAAsB,kBACpB,MACA,aACe;AACf,OAAM,uBAAuB,MAAM,EACjC,SAAS,aACV,CAAC;;AAeJ,eAAsB,uBACpB,MACA,YAC4B;CAC5B,MAAM,UAAU,6BAAA,iBAAiB,WAAW;CAC5C,MAAM,WAAA,GAAA,WAAA,0BAAmC,MAAM,QAAQ,QAAQ;AAG/D,8BAAA,+BAA+B,KAAK;CAGpC,IAAI,oBAAuC;AAC3C,KAAI,CAAC,QAAQ,gBACX,eAAc,6BAAA,4BAA4B,KAAK;AAGjD,KAAI,QAAQ,gBAAgB,eAAe;AACzC,+BAAA,wBAAwB,MAAM,SAAS,QAAQ;AAC/C,+BAAA,2BAA2B,MAAM,QAAQ;;AAG3C,KAAI,CAAC,QAAQ,WACX,QAAA,GAAA,WAAA,aAAkB,KAAK;AAGzB,QAAO"}
@@ -3,4 +3,5 @@ import { GeneratorOptions, NormalizedGeneratorOptions } from "./add-tailwind-con
3
3
  export declare function normalizeOptions(options: GeneratorOptions): NormalizedGeneratorOptions;
4
4
  export declare function detectTailwindInstalledVersion(tree: Tree): "4" | "5" | undefined;
5
5
  export declare function addTailwindRequiredPackages(tree: Tree): GeneratorCallback;
6
+ export declare function writeTailwindPostcssConfig(tree: Tree, project: ProjectConfiguration): void;
6
7
  export declare function updateApplicationStyles(tree: Tree, options: NormalizedGeneratorOptions, project: ProjectConfiguration): void;
@@ -20,10 +20,22 @@ function detectTailwindInstalledVersion(tree) {
20
20
  function addTailwindRequiredPackages(tree) {
21
21
  const pkgVersions = require_tailwind_dependencies.getTailwindDependencies();
22
22
  return (0, _nx_devkit.addDependenciesToPackageJson)(tree, {
23
+ postcss: pkgVersions.postcss,
23
24
  tailwindcss: pkgVersions.tailwindcss,
25
+ "@tailwindcss/postcss": pkgVersions["@tailwindcss/postcss"],
24
26
  "@tailwindcss/vite": pkgVersions["@tailwindcss/vite"]
25
27
  }, {});
26
28
  }
29
+ function writeTailwindPostcssConfig(tree, project) {
30
+ const postcssConfigPath = (0, _nx_devkit.joinPathFragments)(project.root, "postcss.config.mjs");
31
+ if (tree.exists(postcssConfigPath)) return;
32
+ tree.write(postcssConfigPath, `export default {
33
+ plugins: {
34
+ '@tailwindcss/postcss': {},
35
+ },
36
+ };
37
+ `);
38
+ }
27
39
  function updateApplicationStyles(tree, options, project) {
28
40
  let stylesEntryPoint = options.stylesEntryPoint;
29
41
  if (stylesEntryPoint && !tree.exists(stylesEntryPoint)) throw new Error(`The provided styles entry point "${stylesEntryPoint}" could not be found.`);
@@ -58,5 +70,6 @@ exports.addTailwindRequiredPackages = addTailwindRequiredPackages;
58
70
  exports.detectTailwindInstalledVersion = detectTailwindInstalledVersion;
59
71
  exports.normalizeOptions = normalizeOptions;
60
72
  exports.updateApplicationStyles = updateApplicationStyles;
73
+ exports.writeTailwindPostcssConfig = writeTailwindPostcssConfig;
61
74
 
62
75
  //# sourceMappingURL=add-tailwind-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"add-tailwind-helpers.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts"],"sourcesContent":["import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';\nimport { getTailwindDependencies } from '../versions/tailwind-dependencies';\nimport { lt } from 'semver';\nimport {\n Tree,\n readJson,\n addDependenciesToPackageJson,\n GeneratorCallback,\n ProjectConfiguration,\n joinPathFragments,\n stripIndents,\n} from '@nx/devkit';\nimport {\n GeneratorOptions,\n NormalizedGeneratorOptions,\n} from './add-tailwind-config';\n\nexport function normalizeOptions(\n options: GeneratorOptions,\n): NormalizedGeneratorOptions {\n return {\n ...options,\n buildTarget: options.buildTarget || 'build',\n };\n}\n\nexport function detectTailwindInstalledVersion(\n tree: Tree,\n): '4' | '5' | undefined {\n const { dependencies, devDependencies } = readJson(tree, 'package.json');\n const tailwindVersion =\n dependencies?.tailwindcss ?? devDependencies?.tailwindcss;\n\n if (!tailwindVersion) {\n return undefined;\n }\n\n const version = checkAndCleanWithSemver('tailwindcss', tailwindVersion);\n if (lt(version, '4.0.0')) {\n throw new Error(\n `The Tailwind CSS version \"${tailwindVersion}\" is not supported. Please upgrade to v4.0.0 or higher.`,\n );\n }\n return lt(version, '5.0.0') ? '4' : '5';\n}\n\nexport function addTailwindRequiredPackages(tree: Tree): GeneratorCallback {\n const pkgVersions = getTailwindDependencies();\n return addDependenciesToPackageJson(\n tree,\n {\n tailwindcss: pkgVersions.tailwindcss,\n '@tailwindcss/vite': pkgVersions['@tailwindcss/vite'],\n },\n {},\n );\n}\n\nexport function updateApplicationStyles(\n tree: Tree,\n options: NormalizedGeneratorOptions,\n project: ProjectConfiguration,\n): void {\n let stylesEntryPoint = options.stylesEntryPoint;\n\n if (stylesEntryPoint && !tree.exists(stylesEntryPoint)) {\n throw new Error(\n `The provided styles entry point \"${stylesEntryPoint}\" could not be found.`,\n );\n }\n\n if (!stylesEntryPoint) {\n stylesEntryPoint = findStylesEntryPoint(tree, options, project);\n\n if (!stylesEntryPoint) {\n throw new Error(\n stripIndents`Could not find a styles entry point for project \"${options.project}\".\n Please specify a styles entry point using the \"--stylesEntryPoint\" option.`,\n );\n }\n }\n\n if (!stylesEntryPoint.endsWith('.css')) {\n throw new Error(\n `Tailwind CSS v4 is not compatible with any css preprocessors like sass or less. Please use a css file as the styles entry point.`,\n );\n }\n\n const stylesEntryPointContent = tree.read(stylesEntryPoint, 'utf-8');\n\n tree.write(\n stylesEntryPoint,\n stripIndents`@import \"tailwindcss\";\n\n\n ${stylesEntryPointContent}`,\n );\n}\n\nfunction findStylesEntryPoint(\n tree: Tree,\n options: NormalizedGeneratorOptions,\n project: ProjectConfiguration,\n): string | undefined {\n // first check for common names\n const possibleStylesEntryPoints = [\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.css'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.scss'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.sass'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.less'),\n ];\n\n const stylesEntryPoint = possibleStylesEntryPoints.find((s) =>\n tree.exists(s),\n );\n if (stylesEntryPoint) {\n return stylesEntryPoint;\n }\n\n // then check for the specified styles in the build configuration if it exists\n const styles: Array<string | { input: string; inject: boolean }> =\n project.targets?.[options.buildTarget].options?.styles;\n\n if (!styles) {\n return undefined;\n }\n\n // find the first style that belongs to the project source\n const style = styles.find((s) =>\n typeof s === 'string'\n ? s.startsWith(project.root) && tree.exists(s)\n : s.input.startsWith(project.root) &&\n s.inject !== false &&\n tree.exists(s.input),\n );\n\n if (!style) {\n return undefined;\n }\n\n return typeof style === 'string' ? style : style.input;\n}\n"],"mappings":";;;;;AAiBA,SAAgB,iBACd,SAC4B;AAC5B,QAAO;EACL,GAAG;EACH,aAAa,QAAQ,eAAe;EACrC;;AAGH,SAAgB,+BACd,MACuB;CACvB,MAAM,EAAE,cAAc,qBAAA,GAAA,WAAA,UAA6B,MAAM,eAAe;CACxE,MAAM,kBACJ,cAAc,eAAe,iBAAiB;AAEhD,KAAI,CAAC,gBACH;CAGF,MAAM,WAAA,GAAA,4BAAA,yBAAkC,eAAe,gBAAgB;AACvE,MAAA,GAAA,OAAA,IAAO,SAAS,QAAQ,CACtB,OAAM,IAAI,MACR,6BAA6B,gBAAgB,yDAC9C;AAEH,SAAA,GAAA,OAAA,IAAU,SAAS,QAAQ,GAAG,MAAM;;AAGtC,SAAgB,4BAA4B,MAA+B;CACzE,MAAM,cAAc,8BAAA,yBAAyB;AAC7C,SAAA,GAAA,WAAA,8BACE,MACA;EACE,aAAa,YAAY;EACzB,qBAAqB,YAAY;EAClC,EACD,EAAE,CACH;;AAGH,SAAgB,wBACd,MACA,SACA,SACM;CACN,IAAI,mBAAmB,QAAQ;AAE/B,KAAI,oBAAoB,CAAC,KAAK,OAAO,iBAAiB,CACpD,OAAM,IAAI,MACR,oCAAoC,iBAAiB,uBACtD;AAGH,KAAI,CAAC,kBAAkB;AACrB,qBAAmB,qBAAqB,MAAM,SAAS,QAAQ;AAE/D,MAAI,CAAC,iBACH,OAAM,IAAI,MACR,WAAA,YAAY,oDAAoD,QAAQ,QAAQ;oFAEjF;;AAIL,KAAI,CAAC,iBAAiB,SAAS,OAAO,CACpC,OAAM,IAAI,MACR,mIACD;CAGH,MAAM,0BAA0B,KAAK,KAAK,kBAAkB,QAAQ;AAEpE,MAAK,MACH,kBACA,WAAA,YAAY;;;MAGV,0BACH;;AAGH,SAAS,qBACP,MACA,SACA,SACoB;CASpB,MAAM,mBAP4B;oCACd,QAAQ,cAAc,QAAQ,MAAM,aAAa;oCACjD,QAAQ,cAAc,QAAQ,MAAM,cAAc;oCAClD,QAAQ,cAAc,QAAQ,MAAM,cAAc;oCAClD,QAAQ,cAAc,QAAQ,MAAM,cAAc;EACrE,CAEkD,MAAM,MACvD,KAAK,OAAO,EAAE,CACf;AACD,KAAI,iBACF,QAAO;CAIT,MAAM,SACJ,QAAQ,UAAU,QAAQ,aAAa,SAAS;AAElD,KAAI,CAAC,OACH;CAIF,MAAM,QAAQ,OAAO,MAAM,MACzB,OAAO,MAAM,WACT,EAAE,WAAW,QAAQ,KAAK,IAAI,KAAK,OAAO,EAAE,GAC5C,EAAE,MAAM,WAAW,QAAQ,KAAK,IAChC,EAAE,WAAW,SACb,KAAK,OAAO,EAAE,MAAM,CACzB;AAED,KAAI,CAAC,MACH;AAGF,QAAO,OAAO,UAAU,WAAW,QAAQ,MAAM"}
1
+ {"version":3,"file":"add-tailwind-helpers.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/lib/add-tailwind-helpers.ts"],"sourcesContent":["import { checkAndCleanWithSemver } from '@nx/devkit/src/utils/semver';\nimport { getTailwindDependencies } from '../versions/tailwind-dependencies';\nimport { lt } from 'semver';\nimport {\n Tree,\n readJson,\n addDependenciesToPackageJson,\n GeneratorCallback,\n ProjectConfiguration,\n joinPathFragments,\n stripIndents,\n} from '@nx/devkit';\nimport {\n GeneratorOptions,\n NormalizedGeneratorOptions,\n} from './add-tailwind-config';\n\nexport function normalizeOptions(\n options: GeneratorOptions,\n): NormalizedGeneratorOptions {\n return {\n ...options,\n buildTarget: options.buildTarget || 'build',\n };\n}\n\nexport function detectTailwindInstalledVersion(\n tree: Tree,\n): '4' | '5' | undefined {\n const { dependencies, devDependencies } = readJson(tree, 'package.json');\n const tailwindVersion =\n dependencies?.tailwindcss ?? devDependencies?.tailwindcss;\n\n if (!tailwindVersion) {\n return undefined;\n }\n\n const version = checkAndCleanWithSemver('tailwindcss', tailwindVersion);\n if (lt(version, '4.0.0')) {\n throw new Error(\n `The Tailwind CSS version \"${tailwindVersion}\" is not supported. Please upgrade to v4.0.0 or higher.`,\n );\n }\n return lt(version, '5.0.0') ? '4' : '5';\n}\n\nexport function addTailwindRequiredPackages(tree: Tree): GeneratorCallback {\n const pkgVersions = getTailwindDependencies();\n return addDependenciesToPackageJson(\n tree,\n {\n postcss: pkgVersions.postcss,\n tailwindcss: pkgVersions.tailwindcss,\n '@tailwindcss/postcss': pkgVersions['@tailwindcss/postcss'],\n '@tailwindcss/vite': pkgVersions['@tailwindcss/vite'],\n },\n {},\n );\n}\n\nexport function writeTailwindPostcssConfig(\n tree: Tree,\n project: ProjectConfiguration,\n): void {\n const postcssConfigPath = joinPathFragments(\n project.root,\n 'postcss.config.mjs',\n );\n\n if (tree.exists(postcssConfigPath)) {\n return;\n }\n\n tree.write(\n postcssConfigPath,\n `export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n};\n`,\n );\n}\n\nexport function updateApplicationStyles(\n tree: Tree,\n options: NormalizedGeneratorOptions,\n project: ProjectConfiguration,\n): void {\n let stylesEntryPoint = options.stylesEntryPoint;\n\n if (stylesEntryPoint && !tree.exists(stylesEntryPoint)) {\n throw new Error(\n `The provided styles entry point \"${stylesEntryPoint}\" could not be found.`,\n );\n }\n\n if (!stylesEntryPoint) {\n stylesEntryPoint = findStylesEntryPoint(tree, options, project);\n\n if (!stylesEntryPoint) {\n throw new Error(\n stripIndents`Could not find a styles entry point for project \"${options.project}\".\n Please specify a styles entry point using the \"--stylesEntryPoint\" option.`,\n );\n }\n }\n\n if (!stylesEntryPoint.endsWith('.css')) {\n throw new Error(\n `Tailwind CSS v4 is not compatible with any css preprocessors like sass or less. Please use a css file as the styles entry point.`,\n );\n }\n\n const stylesEntryPointContent = tree.read(stylesEntryPoint, 'utf-8');\n\n tree.write(\n stylesEntryPoint,\n stripIndents`@import \"tailwindcss\";\n\n\n ${stylesEntryPointContent}`,\n );\n}\n\nfunction findStylesEntryPoint(\n tree: Tree,\n options: NormalizedGeneratorOptions,\n project: ProjectConfiguration,\n): string | undefined {\n // first check for common names\n const possibleStylesEntryPoints = [\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.css'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.scss'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.sass'),\n joinPathFragments(project.sourceRoot ?? project.root, 'styles.less'),\n ];\n\n const stylesEntryPoint = possibleStylesEntryPoints.find((s) =>\n tree.exists(s),\n );\n if (stylesEntryPoint) {\n return stylesEntryPoint;\n }\n\n // then check for the specified styles in the build configuration if it exists\n const styles: Array<string | { input: string; inject: boolean }> =\n project.targets?.[options.buildTarget].options?.styles;\n\n if (!styles) {\n return undefined;\n }\n\n // find the first style that belongs to the project source\n const style = styles.find((s) =>\n typeof s === 'string'\n ? s.startsWith(project.root) && tree.exists(s)\n : s.input.startsWith(project.root) &&\n s.inject !== false &&\n tree.exists(s.input),\n );\n\n if (!style) {\n return undefined;\n }\n\n return typeof style === 'string' ? style : style.input;\n}\n"],"mappings":";;;;;AAiBA,SAAgB,iBACd,SAC4B;AAC5B,QAAO;EACL,GAAG;EACH,aAAa,QAAQ,eAAe;EACrC;;AAGH,SAAgB,+BACd,MACuB;CACvB,MAAM,EAAE,cAAc,qBAAA,GAAA,WAAA,UAA6B,MAAM,eAAe;CACxE,MAAM,kBACJ,cAAc,eAAe,iBAAiB;AAEhD,KAAI,CAAC,gBACH;CAGF,MAAM,WAAA,GAAA,4BAAA,yBAAkC,eAAe,gBAAgB;AACvE,MAAA,GAAA,OAAA,IAAO,SAAS,QAAQ,CACtB,OAAM,IAAI,MACR,6BAA6B,gBAAgB,yDAC9C;AAEH,SAAA,GAAA,OAAA,IAAU,SAAS,QAAQ,GAAG,MAAM;;AAGtC,SAAgB,4BAA4B,MAA+B;CACzE,MAAM,cAAc,8BAAA,yBAAyB;AAC7C,SAAA,GAAA,WAAA,8BACE,MACA;EACE,SAAS,YAAY;EACrB,aAAa,YAAY;EACzB,wBAAwB,YAAY;EACpC,qBAAqB,YAAY;EAClC,EACD,EAAE,CACH;;AAGH,SAAgB,2BACd,MACA,SACM;CACN,MAAM,qBAAA,GAAA,WAAA,mBACJ,QAAQ,MACR,qBACD;AAED,KAAI,KAAK,OAAO,kBAAkB,CAChC;AAGF,MAAK,MACH,mBACA;;;;;EAMD;;AAGH,SAAgB,wBACd,MACA,SACA,SACM;CACN,IAAI,mBAAmB,QAAQ;AAE/B,KAAI,oBAAoB,CAAC,KAAK,OAAO,iBAAiB,CACpD,OAAM,IAAI,MACR,oCAAoC,iBAAiB,uBACtD;AAGH,KAAI,CAAC,kBAAkB;AACrB,qBAAmB,qBAAqB,MAAM,SAAS,QAAQ;AAE/D,MAAI,CAAC,iBACH,OAAM,IAAI,MACR,WAAA,YAAY,oDAAoD,QAAQ,QAAQ;oFAEjF;;AAIL,KAAI,CAAC,iBAAiB,SAAS,OAAO,CACpC,OAAM,IAAI,MACR,mIACD;CAGH,MAAM,0BAA0B,KAAK,KAAK,kBAAkB,QAAQ;AAEpE,MAAK,MACH,kBACA,WAAA,YAAY;;;MAGV,0BACH;;AAGH,SAAS,qBACP,MACA,SACA,SACoB;CASpB,MAAM,mBAP4B;oCACd,QAAQ,cAAc,QAAQ,MAAM,aAAa;oCACjD,QAAQ,cAAc,QAAQ,MAAM,cAAc;oCAClD,QAAQ,cAAc,QAAQ,MAAM,cAAc;oCAClD,QAAQ,cAAc,QAAQ,MAAM,cAAc;EACrE,CAEkD,MAAM,MACvD,KAAK,OAAO,EAAE,CACf;AACD,KAAI,iBACF,QAAO;CAIT,MAAM,SACJ,QAAQ,UAAU,QAAQ,aAAa,SAAS;AAElD,KAAI,CAAC,OACH;CAIF,MAAM,QAAQ,OAAO,MAAM,MACzB,OAAO,MAAM,WACT,EAAE,WAAW,QAAQ,KAAK,IAAI,KAAK,OAAO,EAAE,GAC5C,EAAE,MAAM,WAAW,QAAQ,KAAK,IAChC,EAAE,WAAW,SACb,KAAK,OAAO,EAAE,MAAM,CACzB;AAED,KAAI,CAAC,MACH;AAGF,QAAO,OAAO,UAAU,WAAW,QAAQ,MAAM"}
@@ -1,9 +1,9 @@
1
1
  export declare const V18_X_NX_DEVKIT = "^20.0.0";
2
2
  export declare const V18_X_NX_ANGULAR = "^20.0.0";
3
- export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.24";
4
- export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.24";
5
- export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.24";
6
- export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.24";
3
+ export declare const V18_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.26";
4
+ export declare const V18_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.26";
5
+ export declare const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.26";
6
+ export declare const V18_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.26";
7
7
  export declare const V18_X_FRONT_MATTER = "^4.0.2";
8
8
  export declare const V18_X_MARKED = "^15.0.7";
9
9
  export declare const V18_X_MARKED_GFM_HEADING_ID = "^4.1.1";
@@ -12,8 +12,10 @@ export declare const V18_X_MARKED_MANGLE = "^1.1.10";
12
12
  export declare const V18_X_MERMAID = "^10.2.4";
13
13
  export declare const V18_X_PRISMJS = "^1.29.0";
14
14
  export declare const V18_X_TAILWINDCSS = "^4.2.2";
15
+ export declare const V18_X_TAILWINDCSS_POSTCSS = "^4.2.2";
15
16
  export declare const V18_X_TAILWINDCSS_VITE = "^4.2.2";
16
- export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.24";
17
+ export declare const V18_X_POSTCSS = "^8.5.6";
18
+ export declare const V18_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.26";
17
19
  export declare const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = "^19.0.0";
18
20
  export declare const V18_X_NX_VITE = "^21.0.0";
19
21
  export declare const V18_X_NX_LINTER = "^21.0.0";
@@ -1,8 +1,12 @@
1
1
  //#region packages/nx-plugin/src/generators/app/versions/nx_18_X/versions.ts
2
2
  var V18_X_TAILWINDCSS = "^4.2.2";
3
+ var V18_X_TAILWINDCSS_POSTCSS = "^4.2.2";
3
4
  var V18_X_TAILWINDCSS_VITE = "^4.2.2";
5
+ var V18_X_POSTCSS = "^8.5.6";
4
6
  //#endregion
7
+ exports.V18_X_POSTCSS = V18_X_POSTCSS;
5
8
  exports.V18_X_TAILWINDCSS = V18_X_TAILWINDCSS;
9
+ exports.V18_X_TAILWINDCSS_POSTCSS = V18_X_TAILWINDCSS_POSTCSS;
6
10
  exports.V18_X_TAILWINDCSS_VITE = V18_X_TAILWINDCSS_VITE;
7
11
 
8
12
  //# sourceMappingURL=versions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../../nx-plugin/src/generators/app/versions/nx_18_X/versions.ts"],"sourcesContent":["// V18_X\n// dependencies\nexport const V18_X_NX_DEVKIT = '^20.0.0';\nexport const V18_X_NX_ANGULAR = '^20.0.0';\nexport const V18_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.24';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.24';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.24';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.24';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^15.0.7';\nexport const V18_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V18_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V18_X_MARKED_MANGLE = '^1.1.10';\nexport const V18_X_MERMAID = '^10.2.4';\nexport const V18_X_PRISMJS = '^1.29.0';\nexport const V18_X_TAILWINDCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.24';\nexport const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = '^19.0.0';\nexport const V18_X_NX_VITE = '^21.0.0';\nexport const V18_X_NX_LINTER = '^21.0.0';\nexport const V18_X_JSDOM = '^22.1.0';\nexport const V18_X_VITE = '^8.0.0';\nexport const V18_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V18_X_VITEST = '^4.0.0';\nexport const V18_X_ZOD = '^3.21.4';\n"],"mappings":";AAeA,IAAa,oBAAoB;AACjC,IAAa,yBAAyB"}
1
+ {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../../nx-plugin/src/generators/app/versions/nx_18_X/versions.ts"],"sourcesContent":["// V18_X\n// dependencies\nexport const V18_X_NX_DEVKIT = '^20.0.0';\nexport const V18_X_NX_ANGULAR = '^20.0.0';\nexport const V18_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.26';\nexport const V18_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.26';\nexport const V18_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.26';\nexport const V18_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.26';\nexport const V18_X_FRONT_MATTER = '^4.0.2';\nexport const V18_X_MARKED = '^15.0.7';\nexport const V18_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V18_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V18_X_MARKED_MANGLE = '^1.1.10';\nexport const V18_X_MERMAID = '^10.2.4';\nexport const V18_X_PRISMJS = '^1.29.0';\nexport const V18_X_TAILWINDCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_POSTCSS = '^4.2.2';\nexport const V18_X_TAILWINDCSS_VITE = '^4.2.2';\nexport const V18_X_POSTCSS = '^8.5.6';\n\n// devDependencies\nexport const V18_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.26';\nexport const V18_X_ANGULAR_DEVKIT_BUILD_ANGULAR = '^19.0.0';\nexport const V18_X_NX_VITE = '^21.0.0';\nexport const V18_X_NX_LINTER = '^21.0.0';\nexport const V18_X_JSDOM = '^22.1.0';\nexport const V18_X_VITE = '^8.0.0';\nexport const V18_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V18_X_VITEST = '^4.0.0';\nexport const V18_X_ZOD = '^3.21.4';\n"],"mappings":";AAeA,IAAa,oBAAoB;AACjC,IAAa,4BAA4B;AACzC,IAAa,yBAAyB;AACtC,IAAa,gBAAgB"}
@@ -1,4 +1,4 @@
1
- declare const tailwindDependencyKeys: readonly ["tailwindcss", "@tailwindcss/vite"];
1
+ declare const tailwindDependencyKeys: readonly ["postcss", "tailwindcss", "@tailwindcss/postcss", "@tailwindcss/vite"];
2
2
  export type TailwindDependency = (typeof tailwindDependencyKeys)[number];
3
3
  export declare const getTailwindDependencies: () => Record<TailwindDependency, string>;
4
4
  export {};
@@ -2,7 +2,9 @@ const require_versions = require("./nx_18_X/versions.js");
2
2
  //#region packages/nx-plugin/src/generators/app/versions/tailwind-dependencies.ts
3
3
  var getTailwindDependencies = () => {
4
4
  return {
5
+ postcss: require_versions.V18_X_POSTCSS,
5
6
  tailwindcss: require_versions.V18_X_TAILWINDCSS,
7
+ "@tailwindcss/postcss": require_versions.V18_X_TAILWINDCSS_POSTCSS,
6
8
  "@tailwindcss/vite": require_versions.V18_X_TAILWINDCSS_VITE
7
9
  };
8
10
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind-dependencies.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/versions/tailwind-dependencies.ts"],"sourcesContent":["import { V18_X_TAILWINDCSS, V18_X_TAILWINDCSS_VITE } from './nx_18_X/versions';\n\nconst tailwindDependencyKeys = ['tailwindcss', '@tailwindcss/vite'] as const;\n\nexport type TailwindDependency = (typeof tailwindDependencyKeys)[number];\n\nexport const getTailwindDependencies = (): Record<\n TailwindDependency,\n string\n> => {\n return {\n tailwindcss: V18_X_TAILWINDCSS,\n '@tailwindcss/vite': V18_X_TAILWINDCSS_VITE,\n };\n};\n"],"mappings":";;AAMA,IAAa,gCAGR;AACH,QAAO;EACL,aAAa,iBAAA;EACb,qBAAqB,iBAAA;EACtB"}
1
+ {"version":3,"file":"tailwind-dependencies.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/generators/app/versions/tailwind-dependencies.ts"],"sourcesContent":["import {\n V18_X_POSTCSS,\n V18_X_TAILWINDCSS,\n V18_X_TAILWINDCSS_POSTCSS,\n V18_X_TAILWINDCSS_VITE,\n} from './nx_18_X/versions';\n\nconst tailwindDependencyKeys = [\n 'postcss',\n 'tailwindcss',\n '@tailwindcss/postcss',\n '@tailwindcss/vite',\n] as const;\n\nexport type TailwindDependency = (typeof tailwindDependencyKeys)[number];\n\nexport const getTailwindDependencies = (): Record<\n TailwindDependency,\n string\n> => {\n return {\n postcss: V18_X_POSTCSS,\n tailwindcss: V18_X_TAILWINDCSS,\n '@tailwindcss/postcss': V18_X_TAILWINDCSS_POSTCSS,\n '@tailwindcss/vite': V18_X_TAILWINDCSS_VITE,\n };\n};\n"],"mappings":";;AAgBA,IAAa,gCAGR;AACH,QAAO;EACL,SAAS,iBAAA;EACT,aAAa,iBAAA;EACb,wBAAwB,iBAAA;EACxB,qBAAqB,iBAAA;EACtB"}
@@ -1,13 +1,13 @@
1
- export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.24";
2
- export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.24";
1
+ export declare const V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.26";
2
+ export declare const V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.26";
3
3
  export declare const V19_X_MARKED = "^15.0.7";
4
4
  export declare const V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
5
5
  export declare const V19_X_MARKED_HIGHLIGHT = "^2.2.1";
6
6
  export declare const V19_X_MARKED_MANGLE = "^1.1.10";
7
7
  export declare const V19_X_PRISMJS = "^1.29.0";
8
- export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.24";
9
- export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.24";
10
- export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.24";
8
+ export declare const V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.26";
9
+ export declare const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.26";
10
+ export declare const V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.26";
11
11
  export declare const V19_X_NX_ANGULAR = "^22.0.0";
12
12
  export declare const V19_X_NX_VITE = "^22.0.0";
13
13
  export declare const V19_X_JSDOM = "^22.0.0";
@@ -1,14 +1,14 @@
1
1
  //#region packages/nx-plugin/src/utils/versions/ng_19_X/versions.ts
2
- var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.24";
3
- var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.24";
2
+ var V19_X_ANALOG_JS_ROUTER = "^3.0.0-alpha.26";
3
+ var V19_X_ANALOG_JS_CONTENT = "^3.0.0-alpha.26";
4
4
  var V19_X_MARKED = "^15.0.7";
5
5
  var V19_X_MARKED_GFM_HEADING_ID = "^4.1.1";
6
6
  var V19_X_MARKED_HIGHLIGHT = "^2.2.1";
7
7
  var V19_X_MARKED_MANGLE = "^1.1.10";
8
8
  var V19_X_PRISMJS = "^1.29.0";
9
- var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.24";
10
- var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.24";
11
- var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.24";
9
+ var V19_X_ANALOG_JS_PLATFORM = "^3.0.0-alpha.26";
10
+ var V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = "^3.0.0-alpha.26";
11
+ var V19_X_ANALOG_JS_VITEST_ANGULAR = "^3.0.0-alpha.26";
12
12
  var V19_X_NX_VITE = "^22.0.0";
13
13
  var V19_X_JSDOM = "^22.0.0";
14
14
  var V19_X_VITE_TSCONFIG_PATHS = "^4.2.0";
@@ -1 +1 @@
1
- {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/utils/versions/ng_19_X/versions.ts"],"sourcesContent":["// V19_X\nexport const V19_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.24';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.24';\nexport const V19_X_MARKED = '^15.0.7';\nexport const V19_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V19_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V19_X_MARKED_MANGLE = '^1.1.10';\nexport const V19_X_PRISMJS = '^1.29.0';\n\n// devDependencies\nexport const V19_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.24';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.24';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.24';\nexport const V19_X_NX_ANGULAR = '^22.0.0';\nexport const V19_X_NX_VITE = '^22.0.0';\nexport const V19_X_JSDOM = '^22.0.0';\nexport const V19_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V19_X_VITEST = '^3.0.0';\nexport const V19_X_VITE = '^6.0.0';\nexport const NX_X_LATEST_VITE = '^8.0.0';\nexport const NX_X_LATEST_VITEST = '^4.0.0';\n"],"mappings":";AACA,IAAa,yBAAyB;AACtC,IAAa,0BAA0B;AACvC,IAAa,eAAe;AAC5B,IAAa,8BAA8B;AAC3C,IAAa,yBAAyB;AACtC,IAAa,sBAAsB;AACnC,IAAa,gBAAgB;AAG7B,IAAa,2BAA2B;AACxC,IAAa,sCAAsC;AACnD,IAAa,iCAAiC;AAE9C,IAAa,gBAAgB;AAC7B,IAAa,cAAc;AAC3B,IAAa,4BAA4B;AACzC,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,mBAAmB;AAChC,IAAa,qBAAqB"}
1
+ {"version":3,"file":"versions.js","names":[],"sources":["../../../../../../../../../nx-plugin/src/utils/versions/ng_19_X/versions.ts"],"sourcesContent":["// V19_X\nexport const V19_X_ANALOG_JS_ROUTER = '^3.0.0-alpha.26';\nexport const V19_X_ANALOG_JS_CONTENT = '^3.0.0-alpha.26';\nexport const V19_X_MARKED = '^15.0.7';\nexport const V19_X_MARKED_GFM_HEADING_ID = '^4.1.1';\nexport const V19_X_MARKED_HIGHLIGHT = '^2.2.1';\nexport const V19_X_MARKED_MANGLE = '^1.1.10';\nexport const V19_X_PRISMJS = '^1.29.0';\n\n// devDependencies\nexport const V19_X_ANALOG_JS_PLATFORM = '^3.0.0-alpha.26';\nexport const V19_X_ANALOG_JS_VITE_PLUGIN_ANGULAR = '^3.0.0-alpha.26';\nexport const V19_X_ANALOG_JS_VITEST_ANGULAR = '^3.0.0-alpha.26';\nexport const V19_X_NX_ANGULAR = '^22.0.0';\nexport const V19_X_NX_VITE = '^22.0.0';\nexport const V19_X_JSDOM = '^22.0.0';\nexport const V19_X_VITE_TSCONFIG_PATHS = '^4.2.0';\nexport const V19_X_VITEST = '^3.0.0';\nexport const V19_X_VITE = '^6.0.0';\nexport const NX_X_LATEST_VITE = '^8.0.0';\nexport const NX_X_LATEST_VITEST = '^4.0.0';\n"],"mappings":";AACA,IAAa,yBAAyB;AACtC,IAAa,0BAA0B;AACvC,IAAa,eAAe;AAC5B,IAAa,8BAA8B;AAC3C,IAAa,yBAAyB;AACtC,IAAa,sBAAsB;AACnC,IAAa,gBAAgB;AAG7B,IAAa,2BAA2B;AACxC,IAAa,sCAAsC;AACnD,IAAa,iCAAiC;AAE9C,IAAa,gBAAgB;AAC7B,IAAa,cAAc;AAC3B,IAAa,4BAA4B;AACzC,IAAa,eAAe;AAC5B,IAAa,aAAa;AAC1B,IAAa,mBAAmB;AAChC,IAAa,qBAAqB"}
@@ -3,6 +3,7 @@ import type { NitroConfig, PrerenderRoute } from "nitro/types";
3
3
  import type { SitemapConfig, SitemapEntry, SitemapExcludeRule, SitemapPriority, SitemapRouteDefinition, SitemapRouteInput, SitemapRouteSource, SitemapTransform, PrerenderContentDir, PrerenderContentFile, PrerenderSitemapConfig, PrerenderRouteConfig } from "@analogjs/vite-plugin-nitro";
4
4
  import type { ContentPluginOptions } from "./content-plugin.js";
5
5
  import type { DebugOption } from "./utils/debug.js";
6
+ import type { StylePipelineOptions } from "./style-pipeline.js";
6
7
  declare module "nitro/types" {
7
8
  interface NitroRouteConfig {
8
9
  ssr?: boolean;
@@ -44,7 +45,7 @@ export interface Options {
44
45
  *
45
46
  * When `false`, the following top-level options are ignored because they
46
47
  * are only forwarded to the internal Angular plugin: `jit`,
47
- * `disableTypeChecking`, `liveReload`, `inlineStylesExtension`,
48
+ * `disableTypeChecking`, `hmr`, `liveReload`, `inlineStylesExtension`,
48
49
  * `fileReplacements`, and `include`.
49
50
  *
50
51
  * Use this to configure the embedded Angular integration itself, not as the
@@ -62,7 +63,13 @@ export interface Options {
62
63
  */
63
64
  inlineStylesExtension?: string;
64
65
  /**
65
- * Enables Angular's HMR during development
66
+ * Enables Angular's HMR during development/watch mode.
67
+ *
68
+ * Defaults to `true` for watch mode.
69
+ */
70
+ hmr?: boolean;
71
+ /**
72
+ * @deprecated Use `hmr` instead. Kept as a compatibility alias.
66
73
  */
67
74
  liveReload?: boolean;
68
75
  /**
@@ -160,6 +167,15 @@ export interface Options {
160
167
  * Inspired by TanStack Router's `routeTree.gen.ts` codegen.
161
168
  */
162
169
  typedRouter?: boolean | TypedRouterOptions;
170
+ /**
171
+ * Experimental slot for community-maintained style-pipeline integrations.
172
+ *
173
+ * This keeps Analog's core surface intentionally narrow: community
174
+ * packages can register Vite plugins through an Analog-first config shape
175
+ * without requiring Analog itself to own design-token engines, library
176
+ * target contracts, or framework-specific theming semantics.
177
+ */
178
+ stylePipeline?: StylePipelineOptions | false;
163
179
  };
164
180
  }
165
181
  export interface TypedRouterOptions {
@@ -8,6 +8,7 @@ import { depsPlugin } from "./deps-plugin.js";
8
8
  import { injectHTMLPlugin } from "./ssr/inject-html-plugin.js";
9
9
  import { serverModePlugin } from "../server-mode-plugin.js";
10
10
  import { routeGenerationPlugin } from "./route-generation-plugin.js";
11
+ import { resolveStylePipelinePlugins } from "./style-pipeline.js";
11
12
  import viteNitroPlugin from "@analogjs/vite-plugin-nitro";
12
13
  import angular from "@analogjs/vite-plugin-angular";
13
14
  import { mapValues, union } from "es-toolkit";
@@ -32,7 +33,8 @@ function platformPlugin(opts = {}) {
32
33
  const useAngularCompilationAPI = platformOptions.experimental?.useAngularCompilationAPI ?? viteOptions?.experimental?.useAngularCompilationAPI;
33
34
  debugPlatform("experimental options resolved", {
34
35
  useAngularCompilationAPI: !!useAngularCompilationAPI,
35
- typedRouter: platformOptions.experimental?.typedRouter
36
+ typedRouter: platformOptions.experimental?.typedRouter,
37
+ stylePipeline: !!platformOptions.experimental?.stylePipeline
36
38
  });
37
39
  let nitroOptions = platformOptions?.nitro;
38
40
  if (nitroOptions?.routeRules) nitroOptions = {
@@ -55,6 +57,7 @@ function platformPlugin(opts = {}) {
55
57
  ...externalPlugins(viteNitroPlugin(platformOptions, nitroOptions)),
56
58
  ...platformOptions.ssr ? [...ssrBuildPlugin(), ...injectHTMLPlugin()] : [],
57
59
  ...!isTest ? depsPlugin(platformOptions) : [],
60
+ ...resolveStylePipelinePlugins(platformOptions.experimental?.stylePipeline, platformOptions.workspaceRoot),
58
61
  ...routerPlugin(platformOptions),
59
62
  routeGenerationPlugin(platformOptions),
60
63
  ...contentPlugin(platformOptions?.content, platformOptions),
@@ -64,10 +67,12 @@ function platformPlugin(opts = {}) {
64
67
  disableTypeChecking: platformOptions.disableTypeChecking,
65
68
  include: [...platformOptions.include ?? [], ...(platformOptions.additionalPagesDirs ?? []).map((pageDir) => `${pageDir}/**/*.page.ts`)],
66
69
  additionalContentDirs: platformOptions.additionalContentDirs,
70
+ hmr: platformOptions.hmr,
67
71
  liveReload: platformOptions.liveReload,
68
72
  inlineStylesExtension: platformOptions.inlineStylesExtension,
69
73
  fileReplacements: platformOptions.fileReplacements,
70
74
  debug: platformOptions.debug,
75
+ stylePipeline: platformOptions.experimental?.stylePipeline?.angularPlugins?.length ? { plugins: platformOptions.experimental.stylePipeline.angularPlugins } : void 0,
71
76
  ...viteOptions ?? {},
72
77
  experimental: {
73
78
  ...viteOptions?.experimental ?? {},
@@ -1 +1 @@
1
- {"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { Plugin } from 'vite';\nimport viteNitroPlugin from '@analogjs/vite-plugin-nitro';\nimport angular from '@analogjs/vite-plugin-angular';\nimport { mapValues, union } from 'es-toolkit';\n\nimport { Options } from './options.js';\nimport {\n activateDeferredDebug,\n applyDebugOption,\n debugPlatform,\n} from './utils/debug.js';\nimport { discoverLibraryRoutes } from './discover-library-routes.js';\nimport { routerPlugin } from './router-plugin.js';\nimport { ssrBuildPlugin } from './ssr/ssr-build-plugin.js';\nimport { contentPlugin } from './content-plugin.js';\nimport { clearClientPageEndpointsPlugin } from './clear-client-page-endpoint.js';\nimport { depsPlugin } from './deps-plugin.js';\nimport { injectHTMLPlugin } from './ssr/inject-html-plugin.js';\nimport { serverModePlugin } from '../server-mode-plugin.js';\nimport { routeGenerationPlugin } from './route-generation-plugin.js';\n\n// Bridge Plugin types from external @analogjs packages that resolve a different vite instance\nfunction externalPlugins(plugins: unknown): Plugin[] {\n return plugins as Plugin[];\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n applyDebugOption(opts.debug, opts.workspaceRoot);\n\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const viteOptions = opts?.vite === false ? undefined : opts?.vite;\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n if (platformOptions.discoverRoutes) {\n const workspaceRoot =\n platformOptions.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd();\n const discovered = discoverLibraryRoutes(workspaceRoot);\n platformOptions.additionalPagesDirs = union(\n platformOptions.additionalPagesDirs ?? [],\n discovered.additionalPagesDirs,\n );\n platformOptions.additionalContentDirs = union(\n platformOptions.additionalContentDirs ?? [],\n discovered.additionalContentDirs,\n );\n platformOptions.additionalAPIDirs = union(\n platformOptions.additionalAPIDirs ?? [],\n discovered.additionalAPIDirs,\n );\n }\n\n const useAngularCompilationAPI =\n platformOptions.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n debugPlatform('experimental options resolved', {\n useAngularCompilationAPI: !!useAngularCompilationAPI,\n typedRouter: platformOptions.experimental?.typedRouter,\n });\n let nitroOptions = platformOptions?.nitro;\n\n if (nitroOptions?.routeRules) {\n nitroOptions = {\n ...nitroOptions,\n routeRules: mapValues(nitroOptions.routeRules, (rule) => ({\n ...rule,\n headers: {\n ...rule.headers,\n 'x-analog-no-ssr': rule?.ssr === false ? 'true' : undefined,\n } as any,\n })),\n };\n }\n\n return [\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n ...externalPlugins(viteNitroPlugin(platformOptions as any, nitroOptions)),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...routerPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(opts?.vite === false\n ? []\n : externalPlugins(\n angular({\n jit: platformOptions.jit,\n workspaceRoot: platformOptions.workspaceRoot,\n // Let the Angular plugin keep its own dev-friendly default unless the\n // app explicitly opts into stricter serve-time diagnostics.\n disableTypeChecking: platformOptions.disableTypeChecking,\n include: [\n ...(platformOptions.include ?? []),\n ...(platformOptions.additionalPagesDirs ?? []).map(\n (pageDir) => `${pageDir}/**/*.page.ts`,\n ),\n ],\n additionalContentDirs: platformOptions.additionalContentDirs,\n liveReload: platformOptions.liveReload,\n inlineStylesExtension: platformOptions.inlineStylesExtension,\n fileReplacements: platformOptions.fileReplacements,\n debug: platformOptions.debug,\n ...(viteOptions ?? {}),\n experimental: {\n ...(viteOptions?.experimental ?? {}),\n useAngularCompilationAPI,\n },\n }),\n )),\n ...serverModePlugin(),\n ...clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,SAAS,gBAAgB,SAA4B;AACnD,QAAO;;AAGT,SAAgB,eAAe,OAAgB,EAAE,EAAY;AAC3D,kBAAiB,KAAK,OAAO,KAAK,cAAc;CAEhD,MAAM,SAAA,QAAA,IAAA,aAAqC,UAAU,CAAC,CAAC,QAAQ,IAAI;CACnE,MAAM,cAAc,MAAM,SAAS,QAAQ,KAAA,IAAY,MAAM;CAC7D,MAAM,EAAE,GAAG,oBAAoB;EAC7B,KAAK;EACL,GAAG;EACJ;AACD,KAAI,gBAAgB,gBAAgB;EAKlC,MAAM,aAAa,sBAHjB,gBAAgB,iBAChB,QAAQ,IAAI,wBACZ,QAAQ,KAAK,CACwC;AACvD,kBAAgB,sBAAsB,MACpC,gBAAgB,uBAAuB,EAAE,EACzC,WAAW,oBACZ;AACD,kBAAgB,wBAAwB,MACtC,gBAAgB,yBAAyB,EAAE,EAC3C,WAAW,sBACZ;AACD,kBAAgB,oBAAoB,MAClC,gBAAgB,qBAAqB,EAAE,EACvC,WAAW,kBACZ;;CAGH,MAAM,2BACJ,gBAAgB,cAAc,4BAC9B,aAAa,cAAc;AAC7B,eAAc,iCAAiC;EAC7C,0BAA0B,CAAC,CAAC;EAC5B,aAAa,gBAAgB,cAAc;EAC5C,CAAC;CACF,IAAI,eAAe,iBAAiB;AAEpC,KAAI,cAAc,WAChB,gBAAe;EACb,GAAG;EACH,YAAY,UAAU,aAAa,aAAa,UAAU;GACxD,GAAG;GACH,SAAS;IACP,GAAG,KAAK;IACR,mBAAmB,MAAM,QAAQ,QAAQ,SAAS,KAAA;IACnD;GACF,EAAE;EACJ;AAGH,QAAO;EACL;GACE,MAAM;GACN,OAAO,GAAG,EAAE,WAAW;AACrB,0BAAsB,QAAQ;;GAEjC;EACD,GAAG,gBAAgB,gBAAgB,iBAAwB,aAAa,CAAC;EACzE,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,aAAa,gBAAgB;EAChC,sBAAsB,gBAAgB;EACtC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,MAAM,SAAS,QACf,EAAE,GACF,gBACE,QAAQ;GACN,KAAK,gBAAgB;GACrB,eAAe,gBAAgB;GAG/B,qBAAqB,gBAAgB;GACrC,SAAS,CACP,GAAI,gBAAgB,WAAW,EAAE,EACjC,IAAI,gBAAgB,uBAAuB,EAAE,EAAE,KAC5C,YAAY,GAAG,QAAQ,eACzB,CACF;GACD,uBAAuB,gBAAgB;GACvC,YAAY,gBAAgB;GAC5B,uBAAuB,gBAAgB;GACvC,kBAAkB,gBAAgB;GAClC,OAAO,gBAAgB;GACvB,GAAI,eAAe,EAAE;GACrB,cAAc;IACZ,GAAI,aAAa,gBAAgB,EAAE;IACnC;IACD;GACF,CAAC,CACH;EACL,GAAG,kBAAkB;EACrB,GAAG,gCAAgC;EACpC"}
1
+ {"version":3,"file":"platform-plugin.js","names":[],"sources":["../../../src/lib/platform-plugin.ts"],"sourcesContent":["import { Plugin } from 'vite';\nimport viteNitroPlugin from '@analogjs/vite-plugin-nitro';\nimport angular from '@analogjs/vite-plugin-angular';\nimport { mapValues, union } from 'es-toolkit';\n\nimport { Options } from './options.js';\nimport {\n activateDeferredDebug,\n applyDebugOption,\n debugPlatform,\n} from './utils/debug.js';\nimport { discoverLibraryRoutes } from './discover-library-routes.js';\nimport { routerPlugin } from './router-plugin.js';\nimport { ssrBuildPlugin } from './ssr/ssr-build-plugin.js';\nimport { contentPlugin } from './content-plugin.js';\nimport { clearClientPageEndpointsPlugin } from './clear-client-page-endpoint.js';\nimport { depsPlugin } from './deps-plugin.js';\nimport { injectHTMLPlugin } from './ssr/inject-html-plugin.js';\nimport { serverModePlugin } from '../server-mode-plugin.js';\nimport { routeGenerationPlugin } from './route-generation-plugin.js';\nimport { resolveStylePipelinePlugins } from './style-pipeline.js';\n\n// Bridge Plugin types from external @analogjs packages that resolve a different vite instance\nfunction externalPlugins(plugins: unknown): Plugin[] {\n return plugins as Plugin[];\n}\n\nexport function platformPlugin(opts: Options = {}): Plugin[] {\n applyDebugOption(opts.debug, opts.workspaceRoot);\n\n const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];\n const viteOptions = opts?.vite === false ? undefined : opts?.vite;\n const { ...platformOptions } = {\n ssr: true,\n ...opts,\n };\n if (platformOptions.discoverRoutes) {\n const workspaceRoot =\n platformOptions.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd();\n const discovered = discoverLibraryRoutes(workspaceRoot);\n platformOptions.additionalPagesDirs = union(\n platformOptions.additionalPagesDirs ?? [],\n discovered.additionalPagesDirs,\n );\n platformOptions.additionalContentDirs = union(\n platformOptions.additionalContentDirs ?? [],\n discovered.additionalContentDirs,\n );\n platformOptions.additionalAPIDirs = union(\n platformOptions.additionalAPIDirs ?? [],\n discovered.additionalAPIDirs,\n );\n }\n\n const useAngularCompilationAPI =\n platformOptions.experimental?.useAngularCompilationAPI ??\n viteOptions?.experimental?.useAngularCompilationAPI;\n debugPlatform('experimental options resolved', {\n useAngularCompilationAPI: !!useAngularCompilationAPI,\n typedRouter: platformOptions.experimental?.typedRouter,\n stylePipeline: !!platformOptions.experimental?.stylePipeline,\n });\n let nitroOptions = platformOptions?.nitro;\n\n if (nitroOptions?.routeRules) {\n nitroOptions = {\n ...nitroOptions,\n routeRules: mapValues(nitroOptions.routeRules, (rule) => ({\n ...rule,\n headers: {\n ...rule.headers,\n 'x-analog-no-ssr': rule?.ssr === false ? 'true' : undefined,\n } as any,\n })),\n };\n }\n\n return [\n {\n name: 'analogjs-debug-activate',\n config(_, { command }) {\n activateDeferredDebug(command);\n },\n },\n ...externalPlugins(viteNitroPlugin(platformOptions as any, nitroOptions)),\n ...(platformOptions.ssr\n ? [...ssrBuildPlugin(), ...injectHTMLPlugin()]\n : []),\n ...(!isTest ? depsPlugin(platformOptions) : []),\n ...resolveStylePipelinePlugins(\n platformOptions.experimental?.stylePipeline,\n platformOptions.workspaceRoot,\n ),\n ...routerPlugin(platformOptions),\n routeGenerationPlugin(platformOptions),\n ...contentPlugin(platformOptions?.content, platformOptions),\n ...(opts?.vite === false\n ? []\n : externalPlugins(\n angular({\n jit: platformOptions.jit,\n workspaceRoot: platformOptions.workspaceRoot,\n // Let the Angular plugin keep its own dev-friendly default unless the\n // app explicitly opts into stricter serve-time diagnostics.\n disableTypeChecking: platformOptions.disableTypeChecking,\n include: [\n ...(platformOptions.include ?? []),\n ...(platformOptions.additionalPagesDirs ?? []).map(\n (pageDir) => `${pageDir}/**/*.page.ts`,\n ),\n ],\n additionalContentDirs: platformOptions.additionalContentDirs,\n hmr: platformOptions.hmr,\n liveReload: platformOptions.liveReload,\n inlineStylesExtension: platformOptions.inlineStylesExtension,\n fileReplacements: platformOptions.fileReplacements,\n debug: platformOptions.debug,\n stylePipeline: platformOptions.experimental?.stylePipeline\n ?.angularPlugins?.length\n ? {\n plugins:\n platformOptions.experimental.stylePipeline.angularPlugins,\n }\n : undefined,\n ...(viteOptions ?? {}),\n experimental: {\n ...(viteOptions?.experimental ?? {}),\n useAngularCompilationAPI,\n },\n }),\n )),\n ...serverModePlugin(),\n ...clearClientPageEndpointsPlugin(),\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,SAAS,gBAAgB,SAA4B;AACnD,QAAO;;AAGT,SAAgB,eAAe,OAAgB,EAAE,EAAY;AAC3D,kBAAiB,KAAK,OAAO,KAAK,cAAc;CAEhD,MAAM,SAAA,QAAA,IAAA,aAAqC,UAAU,CAAC,CAAC,QAAQ,IAAI;CACnE,MAAM,cAAc,MAAM,SAAS,QAAQ,KAAA,IAAY,MAAM;CAC7D,MAAM,EAAE,GAAG,oBAAoB;EAC7B,KAAK;EACL,GAAG;EACJ;AACD,KAAI,gBAAgB,gBAAgB;EAKlC,MAAM,aAAa,sBAHjB,gBAAgB,iBAChB,QAAQ,IAAI,wBACZ,QAAQ,KAAK,CACwC;AACvD,kBAAgB,sBAAsB,MACpC,gBAAgB,uBAAuB,EAAE,EACzC,WAAW,oBACZ;AACD,kBAAgB,wBAAwB,MACtC,gBAAgB,yBAAyB,EAAE,EAC3C,WAAW,sBACZ;AACD,kBAAgB,oBAAoB,MAClC,gBAAgB,qBAAqB,EAAE,EACvC,WAAW,kBACZ;;CAGH,MAAM,2BACJ,gBAAgB,cAAc,4BAC9B,aAAa,cAAc;AAC7B,eAAc,iCAAiC;EAC7C,0BAA0B,CAAC,CAAC;EAC5B,aAAa,gBAAgB,cAAc;EAC3C,eAAe,CAAC,CAAC,gBAAgB,cAAc;EAChD,CAAC;CACF,IAAI,eAAe,iBAAiB;AAEpC,KAAI,cAAc,WAChB,gBAAe;EACb,GAAG;EACH,YAAY,UAAU,aAAa,aAAa,UAAU;GACxD,GAAG;GACH,SAAS;IACP,GAAG,KAAK;IACR,mBAAmB,MAAM,QAAQ,QAAQ,SAAS,KAAA;IACnD;GACF,EAAE;EACJ;AAGH,QAAO;EACL;GACE,MAAM;GACN,OAAO,GAAG,EAAE,WAAW;AACrB,0BAAsB,QAAQ;;GAEjC;EACD,GAAG,gBAAgB,gBAAgB,iBAAwB,aAAa,CAAC;EACzE,GAAI,gBAAgB,MAChB,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,CAAC,GAC5C,EAAE;EACN,GAAI,CAAC,SAAS,WAAW,gBAAgB,GAAG,EAAE;EAC9C,GAAG,4BACD,gBAAgB,cAAc,eAC9B,gBAAgB,cACjB;EACD,GAAG,aAAa,gBAAgB;EAChC,sBAAsB,gBAAgB;EACtC,GAAG,cAAc,iBAAiB,SAAS,gBAAgB;EAC3D,GAAI,MAAM,SAAS,QACf,EAAE,GACF,gBACE,QAAQ;GACN,KAAK,gBAAgB;GACrB,eAAe,gBAAgB;GAG/B,qBAAqB,gBAAgB;GACrC,SAAS,CACP,GAAI,gBAAgB,WAAW,EAAE,EACjC,IAAI,gBAAgB,uBAAuB,EAAE,EAAE,KAC5C,YAAY,GAAG,QAAQ,eACzB,CACF;GACD,uBAAuB,gBAAgB;GACvC,KAAK,gBAAgB;GACrB,YAAY,gBAAgB;GAC5B,uBAAuB,gBAAgB;GACvC,kBAAkB,gBAAgB;GAClC,OAAO,gBAAgB;GACvB,eAAe,gBAAgB,cAAc,eACzC,gBAAgB,SAChB,EACE,SACE,gBAAgB,aAAa,cAAc,gBAC9C,GACD,KAAA;GACJ,GAAI,eAAe,EAAE;GACrB,cAAc;IACZ,GAAI,aAAa,gBAAgB,EAAE;IACnC;IACD;GACF,CAAC,CACH;EACL,GAAG,kBAAkB;EACrB,GAAG,gCAAgC;EACpC"}
@@ -0,0 +1,13 @@
1
+ export interface AnalogRouteIdiomDiagnostic {
2
+ code: string;
3
+ severity: "error" | "warning";
4
+ message: string;
5
+ details?: string;
6
+ }
7
+ export interface AnalyzeAnalogRouteFileOptions {
8
+ filename: string;
9
+ code: string;
10
+ routeFiles?: string[];
11
+ }
12
+ export declare function analyzeAnalogRouteFile(options: AnalyzeAnalogRouteFileOptions): AnalogRouteIdiomDiagnostic[];
13
+ export declare function formatAnalogRouteIdiomDiagnostic(diagnostic: AnalogRouteIdiomDiagnostic, filename: string, workspaceRoot: string): string;
@@ -0,0 +1,160 @@
1
+ import { relative } from "node:path";
2
+ import { normalizePath } from "vite";
3
+ import { parseSync } from "oxc-parser";
4
+ //#region packages/platform/src/lib/route-idiom-diagnostics.ts
5
+ var PAGE_FILE_RE = /\.page\.ts$/;
6
+ var ROUTER_OUTLET_RE = /\bRouterOutlet\b|<router-outlet(?:\s|>)/;
7
+ function analyzeAnalogRouteFile(options) {
8
+ const { filename, code, routeFiles = [] } = options;
9
+ const parseResult = parseSync(filename, code, {
10
+ lang: "ts",
11
+ sourceType: "module",
12
+ range: true,
13
+ showSemanticErrors: true
14
+ });
15
+ const parseDiagnostics = parseResult.errors.map((error) => toParseDiagnostic(error));
16
+ if (parseDiagnostics.some((diagnostic) => diagnostic.severity === "error")) return parseDiagnostics;
17
+ const program = parseResult.program;
18
+ const exportedBindings = collectExportedBindings(program);
19
+ const routeMetaBindingName = exportedBindings.routeMeta;
20
+ const routeJsonLdBindingName = exportedBindings.routeJsonLd;
21
+ const diagnostics = [...parseDiagnostics];
22
+ const routeMetaInfo = routeMetaBindingName ? getRouteMetaInfo(program, routeMetaBindingName) : null;
23
+ if (!exportedBindings.hasDefaultExport && !routeMetaInfo?.hasRedirect) diagnostics.push({
24
+ code: "missing-default-export",
25
+ severity: "warning",
26
+ message: "Route files should default-export the page component, unless they are redirect-only routes.",
27
+ details: "Add `export default class ...` or define `routeMeta.redirectTo` for a redirect route."
28
+ });
29
+ if (exportedBindings.hasDefaultExport && routeMetaInfo?.hasRedirect) diagnostics.push({
30
+ code: "redirect-with-component",
31
+ severity: "warning",
32
+ message: "Redirect routes should not also export a page component.",
33
+ details: "Analog ignores the default export when `routeMeta.redirectTo` is present. Remove the component export or remove the redirect."
34
+ });
35
+ if (routeMetaInfo?.hasRedirect && routeMetaInfo.redirectTo && !routeMetaInfo.redirectTo.startsWith("/")) diagnostics.push({
36
+ code: "relative-redirect",
37
+ severity: "warning",
38
+ message: "`routeMeta.redirectTo` should use an absolute path.",
39
+ details: "Nested redirects are documented to use absolute targets such as `/cities/new-york`."
40
+ });
41
+ if (routeJsonLdBindingName) diagnostics.push({
42
+ code: "legacy-route-jsonld-export",
43
+ severity: "warning",
44
+ message: "Prefer `routeMeta.jsonLd` over the legacy top-level `routeJsonLd` export.",
45
+ details: "Keeping JSON-LD inside `routeMeta` makes the route module easier to read and matches the current docs."
46
+ });
47
+ if (isLikelyLayoutRoute(filename, routeFiles) && !ROUTER_OUTLET_RE.test(code)) diagnostics.push({
48
+ code: "layout-without-router-outlet",
49
+ severity: "warning",
50
+ message: "This route file looks like a layout shell, but it does not reference `RouterOutlet` or `<router-outlet>`.",
51
+ details: "Parent layout pages usually import `RouterOutlet` and render an outlet so child routes have somewhere to mount."
52
+ });
53
+ return diagnostics;
54
+ }
55
+ function formatAnalogRouteIdiomDiagnostic(diagnostic, filename, workspaceRoot) {
56
+ const header = `[Analog] ${toDisplayPath(filename, workspaceRoot)} (${diagnostic.code})`;
57
+ const severity = diagnostic.severity.toUpperCase();
58
+ if (diagnostic.details) return `${header}\n${severity}: ${diagnostic.message}\n${diagnostic.details}`;
59
+ return `${header}\n${severity}: ${diagnostic.message}`;
60
+ }
61
+ function toDisplayPath(filename, workspaceRoot) {
62
+ const normalizedFilename = normalizePath(filename);
63
+ const relativePath = normalizePath(relative(normalizePath(workspaceRoot), normalizedFilename));
64
+ if (relativePath && !relativePath.startsWith("..")) return `/${relativePath}`;
65
+ return normalizedFilename;
66
+ }
67
+ function toParseDiagnostic(error) {
68
+ return {
69
+ code: "oxc-parse",
70
+ severity: error.severity === severityError ? "error" : "warning",
71
+ message: error.message,
72
+ details: error.codeframe ?? error.helpMessage ?? void 0
73
+ };
74
+ }
75
+ var severityError = "Error";
76
+ function isLikelyLayoutRoute(filename, routeFiles) {
77
+ if (!PAGE_FILE_RE.test(filename)) return false;
78
+ const routeStem = filename.replace(PAGE_FILE_RE, "");
79
+ return routeFiles.some((routeFile) => routeFile !== filename && routeFile.startsWith(`${routeStem}/`));
80
+ }
81
+ function getRouteMetaInfo(program, bindingName) {
82
+ const routeMetaNode = unwrapRouteMetaObject(getExportedBindingInitializer(program, bindingName));
83
+ if (!routeMetaNode) return null;
84
+ let redirectTo;
85
+ for (const property of routeMetaNode.properties ?? []) {
86
+ if (property?.type !== "Property") continue;
87
+ if (getPropertyName(property.key) !== "redirectTo") continue;
88
+ redirectTo = getStringValue(property.value);
89
+ }
90
+ return {
91
+ hasRedirect: typeof redirectTo === "string" && redirectTo.length > 0,
92
+ redirectTo
93
+ };
94
+ }
95
+ function unwrapRouteMetaObject(initializer) {
96
+ if (!initializer) return null;
97
+ if (initializer.type === "ObjectExpression") return initializer;
98
+ if (initializer.type === "CallExpression" && initializer.callee?.type === "Identifier" && initializer.callee.name === "defineRouteMeta") {
99
+ const firstArgument = initializer.arguments?.[0];
100
+ return firstArgument?.type === "ObjectExpression" ? firstArgument : null;
101
+ }
102
+ return null;
103
+ }
104
+ function getExportedBindingInitializer(program, bindingName) {
105
+ for (const statement of program.body ?? []) {
106
+ if (statement?.type === "VariableDeclaration") {
107
+ const initializer = getVariableInitializer(statement, bindingName);
108
+ if (initializer) return initializer;
109
+ continue;
110
+ }
111
+ if (statement?.type === "ExportNamedDeclaration" && statement.declaration?.type === "VariableDeclaration") {
112
+ const initializer = getVariableInitializer(statement.declaration, bindingName);
113
+ if (initializer) return initializer;
114
+ }
115
+ }
116
+ }
117
+ function getVariableInitializer(declaration, bindingName) {
118
+ for (const declarator of declaration.declarations ?? []) if (declarator.id?.type === "Identifier" && declarator.id.name === bindingName) return declarator.init;
119
+ }
120
+ function collectExportedBindings(program) {
121
+ let hasDefaultExport = false;
122
+ let routeMeta;
123
+ let routeJsonLd;
124
+ for (const statement of program.body ?? []) {
125
+ if (statement?.type === "ExportDefaultDeclaration") {
126
+ hasDefaultExport = true;
127
+ continue;
128
+ }
129
+ if (statement?.type !== "ExportNamedDeclaration") continue;
130
+ const declaration = statement.declaration;
131
+ if (declaration?.type === "VariableDeclaration") for (const declarator of declaration.declarations ?? []) {
132
+ if (declarator.id?.type !== "Identifier") continue;
133
+ if (declarator.id.name === "routeMeta") routeMeta = "routeMeta";
134
+ if (declarator.id.name === "routeJsonLd") routeJsonLd = "routeJsonLd";
135
+ }
136
+ for (const specifier of statement.specifiers ?? []) {
137
+ if (specifier?.type !== "ExportSpecifier" || specifier.local?.type !== "Identifier") continue;
138
+ if (specifier.exported?.type !== "Identifier") continue;
139
+ if (specifier.exported.name === "routeMeta") routeMeta = specifier.local.name;
140
+ if (specifier.exported.name === "routeJsonLd") routeJsonLd = specifier.local.name;
141
+ }
142
+ }
143
+ return {
144
+ hasDefaultExport,
145
+ routeMeta,
146
+ routeJsonLd
147
+ };
148
+ }
149
+ function getPropertyName(node) {
150
+ if (node?.type === "Identifier") return node.name;
151
+ if ((node?.type === "Literal" || node?.type === "StringLiteral") && typeof node.value === "string") return node.value;
152
+ }
153
+ function getStringValue(node) {
154
+ if ((node?.type === "Literal" || node?.type === "StringLiteral") && typeof node.value === "string") return node.value;
155
+ if (node?.type === "TemplateLiteral" && node.expressions?.length === 0 && node.quasis?.length === 1) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
156
+ }
157
+ //#endregion
158
+ export { analyzeAnalogRouteFile, formatAnalogRouteIdiomDiagnostic };
159
+
160
+ //# sourceMappingURL=route-idiom-diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-idiom-diagnostics.js","names":[],"sources":["../../../src/lib/route-idiom-diagnostics.ts"],"sourcesContent":["import { parseSync, type OxcError, type Severity } from 'oxc-parser';\nimport { relative } from 'node:path';\nimport { normalizePath } from 'vite';\n\nexport interface AnalogRouteIdiomDiagnostic {\n code: string;\n severity: 'error' | 'warning';\n message: string;\n details?: string;\n}\n\nexport interface AnalyzeAnalogRouteFileOptions {\n filename: string;\n code: string;\n routeFiles?: string[];\n}\n\nconst PAGE_FILE_RE = /\\.page\\.ts$/;\nconst ROUTER_OUTLET_RE = /\\bRouterOutlet\\b|<router-outlet(?:\\s|>)/;\n\nexport function analyzeAnalogRouteFile(\n options: AnalyzeAnalogRouteFileOptions,\n): AnalogRouteIdiomDiagnostic[] {\n const { filename, code, routeFiles = [] } = options;\n const parseResult = parseSync(filename, code, {\n lang: 'ts',\n sourceType: 'module',\n range: true,\n showSemanticErrors: true,\n });\n\n const parseDiagnostics = parseResult.errors.map((error) =>\n toParseDiagnostic(error),\n );\n\n if (parseDiagnostics.some((diagnostic) => diagnostic.severity === 'error')) {\n return parseDiagnostics;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const program: any = parseResult.program;\n const exportedBindings = collectExportedBindings(program);\n const routeMetaBindingName = exportedBindings.routeMeta;\n const routeJsonLdBindingName = exportedBindings.routeJsonLd;\n const diagnostics = [...parseDiagnostics];\n\n const routeMetaInfo = routeMetaBindingName\n ? getRouteMetaInfo(program, routeMetaBindingName)\n : null;\n\n if (!exportedBindings.hasDefaultExport && !routeMetaInfo?.hasRedirect) {\n diagnostics.push({\n code: 'missing-default-export',\n severity: 'warning',\n message:\n 'Route files should default-export the page component, unless they are redirect-only routes.',\n details:\n 'Add `export default class ...` or define `routeMeta.redirectTo` for a redirect route.',\n });\n }\n\n if (exportedBindings.hasDefaultExport && routeMetaInfo?.hasRedirect) {\n diagnostics.push({\n code: 'redirect-with-component',\n severity: 'warning',\n message: 'Redirect routes should not also export a page component.',\n details:\n 'Analog ignores the default export when `routeMeta.redirectTo` is present. Remove the component export or remove the redirect.',\n });\n }\n\n if (\n routeMetaInfo?.hasRedirect &&\n routeMetaInfo.redirectTo &&\n !routeMetaInfo.redirectTo.startsWith('/')\n ) {\n diagnostics.push({\n code: 'relative-redirect',\n severity: 'warning',\n message: '`routeMeta.redirectTo` should use an absolute path.',\n details:\n 'Nested redirects are documented to use absolute targets such as `/cities/new-york`.',\n });\n }\n\n if (routeJsonLdBindingName) {\n diagnostics.push({\n code: 'legacy-route-jsonld-export',\n severity: 'warning',\n message:\n 'Prefer `routeMeta.jsonLd` over the legacy top-level `routeJsonLd` export.',\n details:\n 'Keeping JSON-LD inside `routeMeta` makes the route module easier to read and matches the current docs.',\n });\n }\n\n if (\n isLikelyLayoutRoute(filename, routeFiles) &&\n !ROUTER_OUTLET_RE.test(code)\n ) {\n diagnostics.push({\n code: 'layout-without-router-outlet',\n severity: 'warning',\n message:\n 'This route file looks like a layout shell, but it does not reference `RouterOutlet` or `<router-outlet>`.',\n details:\n 'Parent layout pages usually import `RouterOutlet` and render an outlet so child routes have somewhere to mount.',\n });\n }\n\n return diagnostics;\n}\n\nexport function formatAnalogRouteIdiomDiagnostic(\n diagnostic: AnalogRouteIdiomDiagnostic,\n filename: string,\n workspaceRoot: string,\n): string {\n const displayName = toDisplayPath(filename, workspaceRoot);\n const header = `[Analog] ${displayName} (${diagnostic.code})`;\n const severity = diagnostic.severity.toUpperCase();\n\n if (diagnostic.details) {\n return `${header}\\n${severity}: ${diagnostic.message}\\n${diagnostic.details}`;\n }\n\n return `${header}\\n${severity}: ${diagnostic.message}`;\n}\n\nfunction toDisplayPath(filename: string, workspaceRoot: string): string {\n const normalizedFilename = normalizePath(filename);\n const normalizedRoot = normalizePath(workspaceRoot);\n const relativePath = normalizePath(\n relative(normalizedRoot, normalizedFilename),\n );\n\n if (relativePath && !relativePath.startsWith('..')) {\n return `/${relativePath}`;\n }\n\n return normalizedFilename;\n}\n\nfunction toParseDiagnostic(error: OxcError): AnalogRouteIdiomDiagnostic {\n return {\n code: 'oxc-parse',\n severity: error.severity === severityError ? 'error' : 'warning',\n message: error.message,\n details: error.codeframe ?? error.helpMessage ?? undefined,\n };\n}\n\nconst severityError: Severity = 'Error';\n\nfunction isLikelyLayoutRoute(filename: string, routeFiles: string[]): boolean {\n if (!PAGE_FILE_RE.test(filename)) {\n return false;\n }\n\n const routeStem = filename.replace(PAGE_FILE_RE, '');\n return routeFiles.some(\n (routeFile) =>\n routeFile !== filename && routeFile.startsWith(`${routeStem}/`),\n );\n}\n\nfunction getRouteMetaInfo(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n program: any,\n bindingName: string,\n): {\n hasRedirect: boolean;\n redirectTo?: string;\n} | null {\n const initializer = getExportedBindingInitializer(program, bindingName);\n const routeMetaNode = unwrapRouteMetaObject(initializer);\n\n if (!routeMetaNode) {\n return null;\n }\n\n let redirectTo: string | undefined;\n\n for (const property of routeMetaNode.properties ?? []) {\n if (property?.type !== 'Property') {\n continue;\n }\n\n const keyName = getPropertyName(property.key);\n if (keyName !== 'redirectTo') {\n continue;\n }\n\n redirectTo = getStringValue(property.value);\n }\n\n return {\n hasRedirect: typeof redirectTo === 'string' && redirectTo.length > 0,\n redirectTo,\n };\n}\n\nfunction unwrapRouteMetaObject(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n initializer: any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any | null {\n if (!initializer) {\n return null;\n }\n\n if (initializer.type === 'ObjectExpression') {\n return initializer;\n }\n\n if (\n initializer.type === 'CallExpression' &&\n initializer.callee?.type === 'Identifier' &&\n initializer.callee.name === 'defineRouteMeta'\n ) {\n const firstArgument = initializer.arguments?.[0];\n return firstArgument?.type === 'ObjectExpression' ? firstArgument : null;\n }\n\n return null;\n}\n\nfunction getExportedBindingInitializer(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n program: any,\n bindingName: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any | undefined {\n for (const statement of program.body ?? []) {\n if (statement?.type === 'VariableDeclaration') {\n const initializer = getVariableInitializer(statement, bindingName);\n if (initializer) {\n return initializer;\n }\n continue;\n }\n\n if (\n statement?.type === 'ExportNamedDeclaration' &&\n statement.declaration?.type === 'VariableDeclaration'\n ) {\n const initializer = getVariableInitializer(\n statement.declaration,\n bindingName,\n );\n if (initializer) {\n return initializer;\n }\n }\n }\n\n return undefined;\n}\n\nfunction getVariableInitializer(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n declaration: any,\n bindingName: string,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any | undefined {\n for (const declarator of declaration.declarations ?? []) {\n if (\n declarator.id?.type === 'Identifier' &&\n declarator.id.name === bindingName\n ) {\n return declarator.init;\n }\n }\n\n return undefined;\n}\n\nfunction collectExportedBindings(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n program: any,\n): {\n hasDefaultExport: boolean;\n routeMeta?: string;\n routeJsonLd?: string;\n} {\n let hasDefaultExport = false;\n let routeMeta: string | undefined;\n let routeJsonLd: string | undefined;\n\n for (const statement of program.body ?? []) {\n if (statement?.type === 'ExportDefaultDeclaration') {\n hasDefaultExport = true;\n continue;\n }\n\n if (statement?.type !== 'ExportNamedDeclaration') {\n continue;\n }\n\n const declaration = statement.declaration;\n if (declaration?.type === 'VariableDeclaration') {\n for (const declarator of declaration.declarations ?? []) {\n if (declarator.id?.type !== 'Identifier') {\n continue;\n }\n\n if (declarator.id.name === 'routeMeta') {\n routeMeta = 'routeMeta';\n }\n\n if (declarator.id.name === 'routeJsonLd') {\n routeJsonLd = 'routeJsonLd';\n }\n }\n }\n\n for (const specifier of statement.specifiers ?? []) {\n if (\n specifier?.type !== 'ExportSpecifier' ||\n specifier.local?.type !== 'Identifier'\n ) {\n continue;\n }\n\n if (specifier.exported?.type !== 'Identifier') {\n continue;\n }\n\n if (specifier.exported.name === 'routeMeta') {\n routeMeta = specifier.local.name;\n }\n\n if (specifier.exported.name === 'routeJsonLd') {\n routeJsonLd = specifier.local.name;\n }\n }\n }\n\n return {\n hasDefaultExport,\n routeMeta,\n routeJsonLd,\n };\n}\n\nfunction getPropertyName(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: any,\n): string | undefined {\n if (node?.type === 'Identifier') {\n return node.name;\n }\n\n if (\n (node?.type === 'Literal' || node?.type === 'StringLiteral') &&\n typeof node.value === 'string'\n ) {\n return node.value;\n }\n\n return undefined;\n}\n\nfunction getStringValue(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n node: any,\n): string | undefined {\n if (\n (node?.type === 'Literal' || node?.type === 'StringLiteral') &&\n typeof node.value === 'string'\n ) {\n return node.value;\n }\n\n if (\n node?.type === 'TemplateLiteral' &&\n node.expressions?.length === 0 &&\n node.quasis?.length === 1\n ) {\n return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;\n }\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
+ import { analyzeAnalogRouteFile, formatAnalogRouteIdiomDiagnostic } from "./route-idiom-diagnostics.js";
1
2
  import { isAbsolute, relative, resolve } from "node:path";
3
+ import { readFileSync } from "node:fs";
2
4
  import { normalizePath } from "vite";
3
5
  import { globSync } from "tinyglobby";
4
6
  //#region packages/platform/src/lib/router-plugin.ts
@@ -45,8 +47,10 @@ function routerPlugin(options) {
45
47
  ...additionalContentDirs
46
48
  ];
47
49
  let routeFilesCache;
50
+ let pageRouteFilesCache;
48
51
  let contentRouteFilesCache;
49
52
  let endpointFilesCache;
53
+ const routeDiagnosticCache = /* @__PURE__ */ new Map();
50
54
  const isRouteLikeFile = (path) => {
51
55
  const normalizedPath = normalizePath(path);
52
56
  return getRouteLikeDirs().some((dir) => normalizedPath === dir || normalizedPath.startsWith(`${dir}/`));
@@ -82,11 +86,36 @@ function routerPlugin(options) {
82
86
  });
83
87
  return endpointFilesCache;
84
88
  };
89
+ const discoverPageRouteFiles = () => {
90
+ pageRouteFilesCache ??= discoverRouteFiles().filter((file) => file.endsWith(".page.ts"));
91
+ return pageRouteFilesCache;
92
+ };
85
93
  const invalidateDiscoveryCaches = () => {
86
94
  routeFilesCache = void 0;
95
+ pageRouteFilesCache = void 0;
87
96
  contentRouteFilesCache = void 0;
88
97
  endpointFilesCache = void 0;
89
98
  };
99
+ const reportRouteDiagnostics = (path) => {
100
+ if (!path.endsWith(".page.ts")) return;
101
+ try {
102
+ const rendered = analyzeAnalogRouteFile({
103
+ filename: path,
104
+ code: readFileSync(path, "utf-8"),
105
+ routeFiles: discoverPageRouteFiles()
106
+ }).map((diagnostic) => formatAnalogRouteIdiomDiagnostic(diagnostic, path, workspaceRoot));
107
+ const fingerprint = rendered.join("\n\n");
108
+ if (!fingerprint) {
109
+ routeDiagnosticCache.delete(path);
110
+ return;
111
+ }
112
+ if (routeDiagnosticCache.get(path) === fingerprint) return;
113
+ routeDiagnosticCache.set(path, fingerprint);
114
+ rendered.forEach((message) => console.warn(message));
115
+ } catch {
116
+ routeDiagnosticCache.delete(path);
117
+ }
118
+ };
90
119
  const getModuleKey = (module) => {
91
120
  if (!root) return `/${normalizePath(relative(workspaceRoot, module))}`;
92
121
  const relToRoot = normalizePath(relative(root, module));
@@ -115,6 +144,11 @@ function routerPlugin(options) {
115
144
  function invalidateRoutes(path, event) {
116
145
  if (!isRouteLikeFile(path)) return;
117
146
  if (event !== "change") invalidateDiscoveryCaches();
147
+ if (event === "change") reportRouteDiagnostics(path);
148
+ else if (event === "unlink") {
149
+ routeDiagnosticCache.delete(path);
150
+ discoverPageRouteFiles().forEach((file) => reportRouteDiagnostics(file));
151
+ } else discoverPageRouteFiles().forEach((file) => reportRouteDiagnostics(file));
118
152
  invalidateFileModules(server, path);
119
153
  if (event === "change") return;
120
154
  server.moduleGraph.fileToModulesMap.forEach((mods) => {
@@ -127,12 +161,19 @@ function routerPlugin(options) {
127
161
  }
128
162
  });
129
163
  });
164
+ server.ws.send("analog:debug-full-reload", {
165
+ plugin: "platform:router-plugin",
166
+ reason: "route-graph-shape-changed",
167
+ event,
168
+ path
169
+ });
130
170
  server.ws.send({ type: "full-reload" });
131
171
  }
132
172
  server.watcher.on("add", (path) => invalidateRoutes(path, "add"));
133
173
  server.watcher.on("change", (path) => invalidateRoutes(path, "change"));
134
174
  server.watcher.on("unlink", (path) => invalidateRoutes(path, "unlink"));
135
175
  for (const dir of [...additionalPagesDirs, ...additionalContentDirs]) server.watcher.add(dir);
176
+ discoverPageRouteFiles().forEach((file) => reportRouteDiagnostics(file));
136
177
  }
137
178
  },
138
179
  {
@@ -1 +1 @@
1
- {"version":3,"file":"router-plugin.js","names":[],"sources":["../../../src/lib/router-plugin.ts"],"sourcesContent":["import { normalizePath, Plugin, UserConfig, ViteDevServer } from 'vite';\nimport { globSync } from 'tinyglobby';\nimport { isAbsolute, relative, resolve } from 'node:path';\n\nimport { Options } from './options.js';\n\n/**\n * Router plugin that handles route file discovery and hot module replacement.\n *\n * This plugin provides three main functionalities:\n * 1. Route invalidation when files are added/deleted (HMR workaround)\n * 2. Dynamic route file discovery and import generation\n * 3. Content route file discovery for markdown and Analog content\n *\n * @param options Configuration options for the router plugin\n * @returns Array of Vite plugins for route handling\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key behavior with { dot: true, absolute: true }:\n * - Returns absolute paths for ALL discovered files\n * - Path normalization is required to match expected output format\n * - Files within project root must use relative paths in object keys\n * - Files outside project root keep absolute paths in object keys\n */\nexport function routerPlugin(options?: Options): Plugin[] {\n const workspaceRoot = normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: UserConfig;\n let root: string;\n // Option dirs are workspace-relative, often written with a leading `/`.\n // Normalize them once into absolute workspace paths so watcher events,\n // glob patterns, and key generation all compare against the same shape.\n const normalizeWatchedDir = (dir: string) => {\n const normalizedDir = normalizePath(\n dir.startsWith(`${workspaceRoot}/`) || dir === workspaceRoot\n ? dir\n : dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalizedDir.endsWith('/')\n ? normalizedDir.slice(0, -1)\n : normalizedDir;\n };\n // Computed eagerly — these depend only on `options` and `workspaceRoot`,\n // both of which are fixed at construction time.\n const additionalPagesDirs = (options?.additionalPagesDirs || []).map((dir) =>\n normalizeWatchedDir(dir),\n );\n const additionalContentDirs = (options?.additionalContentDirs || []).map(\n (dir) => normalizeWatchedDir(dir),\n );\n // Returns every directory that can contain route-like files. The root-\n // relative entries are only available after the Vite `config` hook sets\n // `root`. The short-form fallbacks (`/app/routes`, etc.) let watcher\n // events match before `config` runs — they cover the common convention\n // where paths start with these prefixes.\n const getRouteLikeDirs = () => [\n ...(root\n ? [\n normalizeWatchedDir(`${root}/app/routes`),\n normalizeWatchedDir(`${root}/src/app/routes`),\n normalizeWatchedDir(`${root}/src/app/pages`),\n normalizeWatchedDir(`${root}/src/content`),\n ]\n : []),\n '/app/routes',\n '/src/app/routes',\n '/src/app/pages',\n '/src/content',\n ...additionalPagesDirs,\n ...additionalContentDirs,\n ];\n // These lists are used repeatedly by transform hooks during serve. Keeping\n // them warm avoids a full glob on every route/content invalidation.\n let routeFilesCache: string[] | undefined;\n let contentRouteFilesCache: string[] | undefined;\n let endpointFilesCache: string[] | undefined;\n const isRouteLikeFile = (path: string) => {\n // Watcher paths from chokidar are already absolute — `normalizePath`\n // (forward-slash only) is sufficient; `resolve()` would be a no-op.\n const normalizedPath = normalizePath(path);\n\n return getRouteLikeDirs().some(\n (dir) => normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n };\n const discoverRouteFiles = () => {\n routeFilesCache ??= globSync(\n [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.page.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return routeFilesCache;\n };\n const discoverContentRouteFiles = () => {\n contentRouteFilesCache ??= globSync(\n [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ...additionalContentDirs.map((dir) => `${dir}/**/*.md`),\n ],\n { dot: true, absolute: true },\n );\n\n return contentRouteFilesCache;\n };\n const discoverEndpointFiles = () => {\n endpointFilesCache ??= globSync(\n [\n `${root}/src/app/pages/**/*.server.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.server.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return endpointFilesCache;\n };\n const invalidateDiscoveryCaches = () => {\n routeFilesCache = undefined;\n contentRouteFilesCache = undefined;\n endpointFilesCache = undefined;\n };\n const getModuleKey = (module: string) => {\n // Before config sets `root`, fall back to workspace-relative keys.\n if (!root) {\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n }\n\n const relToRoot = normalizePath(relative(root, module));\n // Use true path containment instead of a raw prefix check so siblings like\n // `/apps/my-app-tools/...` are not mistaken for files inside `/apps/my-app`.\n const isInRoot = !relToRoot.startsWith('..') && !isAbsolute(relToRoot);\n\n if (isInRoot) {\n return `/${relToRoot}`;\n }\n\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n };\n const invalidateFileModules = (server: ViteDevServer, path: string) => {\n const normalizedPath = normalizePath(path);\n // A newly added page can be discovered before its final contents settle.\n // Invalidate the page module itself so later edits don't keep serving the\n // first incomplete transform from Vite's module graph.\n const fileModules =\n server.moduleGraph.getModulesByFile?.(normalizedPath) ??\n server.moduleGraph.fileToModulesMap.get(normalizedPath);\n\n fileModules?.forEach((mod) => {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n });\n };\n\n return [\n {\n name: 'analogjs-router-invalidate-routes',\n configureServer(server) {\n /**\n * Invalidates route modules when files are added or deleted.\n * This is a workaround for Vite's HMR limitations with dynamic imports.\n *\n * @param path The file path that was added or deleted\n */\n function invalidateRoutes(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ) {\n if (!isRouteLikeFile(path)) {\n return;\n }\n\n // Add/remove changes the route graph shape, so the discovery caches\n // must be rebuilt. Plain edits can keep using the current file set.\n if (event !== 'change') {\n invalidateDiscoveryCaches();\n }\n\n invalidateFileModules(server, path);\n\n // For an in-place edit we only need module invalidation. Keeping the\n // app alive here lets Angular/Vite attempt the narrower HMR path.\n if (event === 'change') {\n return;\n }\n\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (mod.id?.includes('analogjs') && mod.id?.includes('fesm')) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send({\n type: 'full-reload',\n });\n }\n\n server.watcher.on('add', (path) => invalidateRoutes(path, 'add'));\n server.watcher.on('change', (path) => invalidateRoutes(path, 'change'));\n server.watcher.on('unlink', (path) => invalidateRoutes(path, 'unlink'));\n\n // Vite's watcher only covers the app root by default.\n // additionalPagesDirs / additionalContentDirs live outside the\n // root (e.g. libs/shared/feature in a monorepo), so file\n // add/rename/delete events are never fired for them. Explicitly\n // add these directories to chokidar so route invalidation works.\n for (const dir of [...additionalPagesDirs, ...additionalContentDirs]) {\n server.watcher.add(dir);\n }\n },\n },\n {\n name: 'analog-glob-routes',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the route replacements.\n enforce: 'post',\n config(_config) {\n config = _config;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n },\n /**\n * Transforms code to replace ANALOG_ROUTE_FILES and ANALOG_CONTENT_ROUTE_FILES\n * placeholders with actual dynamic imports of discovered route and content files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n // Vite 8 / Rolldown filtered transform: the `filter.code` substring\n // pre-filter lets the bundler skip modules that don't contain the\n // marker. '_ROUTE_FILES' is a common substring of both\n // 'ANALOG_ROUTE_FILES' and 'ANALOG_CONTENT_ROUTE_FILES'.\n //\n // IMPORTANT: Do NOT change this to 'ANALOG_ROUTE_FILES' — that is NOT\n // a substring of 'ANALOG_CONTENT_ROUTE_FILES' (they diverge at\n // position 7: 'ANALOG_C...' vs 'ANALOG_R...'). When tsconfig path\n // aliases resolve @analogjs/router and @analogjs/router/content to\n // separate source files, each variable lives in its own module and\n // the filter must match both independently.\n transform: {\n filter: {\n code: '_ROUTE_FILES',\n },\n handler(code) {\n if (\n code.includes('ANALOG_ROUTE_FILES') ||\n code.includes('ANALOG_CONTENT_ROUTE_FILES')\n ) {\n // Discover route files using tinyglobby\n // NOTE: { absolute: true } returns absolute paths for ALL files\n const routeFiles = discoverRouteFiles();\n\n // Discover content files using tinyglobby\n const contentRouteFiles = discoverContentRouteFiles();\n\n let result = code.replace(\n 'ANALOG_ROUTE_FILES = {};',\n `\n ANALOG_ROUTE_FILES = {${routeFiles.map((module) => {\n // Keys are app-root-relative for in-app files,\n // workspace-relative for library files (additionalPagesDirs).\n // import() keeps absolute paths for Vite's module resolution.\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_ROUTE_FILES = {};',\n `\n ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}?analog-content-file=true').then(m => m.default)`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_FILE_COUNT = 0',\n `ANALOG_CONTENT_FILE_COUNT = ${contentRouteFiles.length}`,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analog-glob-endpoints',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the endpoint replacements.\n enforce: 'post',\n /**\n * Transforms code to replace ANALOG_PAGE_ENDPOINTS placeholder\n * with actual dynamic imports of discovered server endpoint files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n transform: {\n filter: {\n code: 'ANALOG_PAGE_ENDPOINTS',\n },\n handler(code) {\n if (code.includes('ANALOG_PAGE_ENDPOINTS')) {\n // Discover server endpoint files using tinyglobby\n const endpointFiles = discoverEndpointFiles();\n\n const result = code.replace(\n 'ANALOG_PAGE_ENDPOINTS = {};',\n `\n ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n ];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"router-plugin.js","names":[],"sources":["../../../src/lib/router-plugin.ts"],"sourcesContent":["import { normalizePath, Plugin, UserConfig, ViteDevServer } from 'vite';\nimport { globSync } from 'tinyglobby';\nimport { readFileSync } from 'node:fs';\nimport { isAbsolute, relative, resolve } from 'node:path';\n\nimport { Options } from './options.js';\nimport {\n analyzeAnalogRouteFile,\n formatAnalogRouteIdiomDiagnostic,\n} from './route-idiom-diagnostics.js';\n\n/**\n * Router plugin that handles route file discovery and hot module replacement.\n *\n * This plugin provides three main functionalities:\n * 1. Route invalidation when files are added/deleted (HMR workaround)\n * 2. Dynamic route file discovery and import generation\n * 3. Content route file discovery for markdown and Analog content\n *\n * @param options Configuration options for the router plugin\n * @returns Array of Vite plugins for route handling\n *\n * IMPORTANT: This plugin uses tinyglobby for file discovery.\n * Key behavior with { dot: true, absolute: true }:\n * - Returns absolute paths for ALL discovered files\n * - Path normalization is required to match expected output format\n * - Files within project root must use relative paths in object keys\n * - Files outside project root keep absolute paths in object keys\n */\nexport function routerPlugin(options?: Options): Plugin[] {\n const workspaceRoot = normalizePath(\n options?.workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n );\n let config: UserConfig;\n let root: string;\n // Option dirs are workspace-relative, often written with a leading `/`.\n // Normalize them once into absolute workspace paths so watcher events,\n // glob patterns, and key generation all compare against the same shape.\n const normalizeWatchedDir = (dir: string) => {\n const normalizedDir = normalizePath(\n dir.startsWith(`${workspaceRoot}/`) || dir === workspaceRoot\n ? dir\n : dir.startsWith('/')\n ? `${workspaceRoot}${dir}`\n : resolve(workspaceRoot, dir),\n );\n return normalizedDir.endsWith('/')\n ? normalizedDir.slice(0, -1)\n : normalizedDir;\n };\n // Computed eagerly — these depend only on `options` and `workspaceRoot`,\n // both of which are fixed at construction time.\n const additionalPagesDirs = (options?.additionalPagesDirs || []).map((dir) =>\n normalizeWatchedDir(dir),\n );\n const additionalContentDirs = (options?.additionalContentDirs || []).map(\n (dir) => normalizeWatchedDir(dir),\n );\n // Returns every directory that can contain route-like files. The root-\n // relative entries are only available after the Vite `config` hook sets\n // `root`. The short-form fallbacks (`/app/routes`, etc.) let watcher\n // events match before `config` runs — they cover the common convention\n // where paths start with these prefixes.\n const getRouteLikeDirs = () => [\n ...(root\n ? [\n normalizeWatchedDir(`${root}/app/routes`),\n normalizeWatchedDir(`${root}/src/app/routes`),\n normalizeWatchedDir(`${root}/src/app/pages`),\n normalizeWatchedDir(`${root}/src/content`),\n ]\n : []),\n '/app/routes',\n '/src/app/routes',\n '/src/app/pages',\n '/src/content',\n ...additionalPagesDirs,\n ...additionalContentDirs,\n ];\n // These lists are used repeatedly by transform hooks during serve. Keeping\n // them warm avoids a full glob on every route/content invalidation.\n let routeFilesCache: string[] | undefined;\n let pageRouteFilesCache: string[] | undefined;\n let contentRouteFilesCache: string[] | undefined;\n let endpointFilesCache: string[] | undefined;\n const routeDiagnosticCache = new Map<string, string>();\n const isRouteLikeFile = (path: string) => {\n // Watcher paths from chokidar are already absolute — `normalizePath`\n // (forward-slash only) is sufficient; `resolve()` would be a no-op.\n const normalizedPath = normalizePath(path);\n\n return getRouteLikeDirs().some(\n (dir) => normalizedPath === dir || normalizedPath.startsWith(`${dir}/`),\n );\n };\n const discoverRouteFiles = () => {\n routeFilesCache ??= globSync(\n [\n `${root}/app/routes/**/*.ts`,\n `${root}/src/app/routes/**/*.ts`,\n `${root}/src/app/pages/**/*.page.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.page.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return routeFilesCache;\n };\n const discoverContentRouteFiles = () => {\n contentRouteFilesCache ??= globSync(\n [\n `${root}/src/app/routes/**/*.md`,\n `${root}/src/app/pages/**/*.md`,\n `${root}/src/content/**/*.md`,\n ...additionalContentDirs.map((dir) => `${dir}/**/*.md`),\n ],\n { dot: true, absolute: true },\n );\n\n return contentRouteFilesCache;\n };\n const discoverEndpointFiles = () => {\n endpointFilesCache ??= globSync(\n [\n `${root}/src/app/pages/**/*.server.ts`,\n ...additionalPagesDirs.map((dir) => `${dir}/**/*.server.ts`),\n ],\n { dot: true, absolute: true },\n );\n\n return endpointFilesCache;\n };\n const discoverPageRouteFiles = () => {\n pageRouteFilesCache ??= discoverRouteFiles().filter((file) =>\n file.endsWith('.page.ts'),\n );\n\n return pageRouteFilesCache;\n };\n const invalidateDiscoveryCaches = () => {\n routeFilesCache = undefined;\n pageRouteFilesCache = undefined;\n contentRouteFilesCache = undefined;\n endpointFilesCache = undefined;\n };\n const reportRouteDiagnostics = (path: string) => {\n if (!path.endsWith('.page.ts')) {\n return;\n }\n\n try {\n const code = readFileSync(path, 'utf-8');\n const routeFiles = discoverPageRouteFiles();\n const diagnostics = analyzeAnalogRouteFile({\n filename: path,\n code,\n routeFiles,\n });\n\n const rendered = diagnostics.map((diagnostic) =>\n formatAnalogRouteIdiomDiagnostic(diagnostic, path, workspaceRoot),\n );\n const fingerprint = rendered.join('\\n\\n');\n\n if (!fingerprint) {\n routeDiagnosticCache.delete(path);\n return;\n }\n\n if (routeDiagnosticCache.get(path) === fingerprint) {\n return;\n }\n\n routeDiagnosticCache.set(path, fingerprint);\n rendered.forEach((message) => console.warn(message));\n } catch {\n routeDiagnosticCache.delete(path);\n }\n };\n const getModuleKey = (module: string) => {\n // Before config sets `root`, fall back to workspace-relative keys.\n if (!root) {\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n }\n\n const relToRoot = normalizePath(relative(root, module));\n // Use true path containment instead of a raw prefix check so siblings like\n // `/apps/my-app-tools/...` are not mistaken for files inside `/apps/my-app`.\n const isInRoot = !relToRoot.startsWith('..') && !isAbsolute(relToRoot);\n\n if (isInRoot) {\n return `/${relToRoot}`;\n }\n\n return `/${normalizePath(relative(workspaceRoot, module))}`;\n };\n const invalidateFileModules = (server: ViteDevServer, path: string) => {\n const normalizedPath = normalizePath(path);\n // A newly added page can be discovered before its final contents settle.\n // Invalidate the page module itself so later edits don't keep serving the\n // first incomplete transform from Vite's module graph.\n const fileModules =\n server.moduleGraph.getModulesByFile?.(normalizedPath) ??\n server.moduleGraph.fileToModulesMap.get(normalizedPath);\n\n fileModules?.forEach((mod) => {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n });\n };\n\n return [\n {\n name: 'analogjs-router-invalidate-routes',\n configureServer(server) {\n /**\n * Invalidates route modules when files are added or deleted.\n * This is a workaround for Vite's HMR limitations with dynamic imports.\n *\n * @param path The file path that was added or deleted\n */\n function invalidateRoutes(\n path: string,\n event: 'add' | 'change' | 'unlink',\n ) {\n if (!isRouteLikeFile(path)) {\n return;\n }\n\n // Add/remove changes the route graph shape, so the discovery caches\n // must be rebuilt. Plain edits can keep using the current file set.\n if (event !== 'change') {\n invalidateDiscoveryCaches();\n }\n\n if (event === 'change') {\n reportRouteDiagnostics(path);\n } else if (event === 'unlink') {\n routeDiagnosticCache.delete(path);\n discoverPageRouteFiles().forEach((file) =>\n reportRouteDiagnostics(file),\n );\n } else {\n discoverPageRouteFiles().forEach((file) =>\n reportRouteDiagnostics(file),\n );\n }\n\n invalidateFileModules(server, path);\n\n // For an in-place edit we only need module invalidation. Keeping the\n // app alive here lets Angular/Vite attempt the narrower HMR path.\n if (event === 'change') {\n return;\n }\n\n server.moduleGraph.fileToModulesMap.forEach((mods) => {\n mods.forEach((mod) => {\n if (mod.id?.includes('analogjs') && mod.id?.includes('fesm')) {\n server.moduleGraph.invalidateModule(mod);\n\n mod.importers.forEach((imp) => {\n server.moduleGraph.invalidateModule(imp);\n });\n }\n });\n });\n\n server.ws.send('analog:debug-full-reload', {\n plugin: 'platform:router-plugin',\n reason: 'route-graph-shape-changed',\n event,\n path,\n });\n server.ws.send({\n type: 'full-reload',\n });\n }\n\n server.watcher.on('add', (path) => invalidateRoutes(path, 'add'));\n server.watcher.on('change', (path) => invalidateRoutes(path, 'change'));\n server.watcher.on('unlink', (path) => invalidateRoutes(path, 'unlink'));\n\n // Vite's watcher only covers the app root by default.\n // additionalPagesDirs / additionalContentDirs live outside the\n // root (e.g. libs/shared/feature in a monorepo), so file\n // add/rename/delete events are never fired for them. Explicitly\n // add these directories to chokidar so route invalidation works.\n for (const dir of [...additionalPagesDirs, ...additionalContentDirs]) {\n server.watcher.add(dir);\n }\n\n discoverPageRouteFiles().forEach((file) =>\n reportRouteDiagnostics(file),\n );\n },\n },\n {\n name: 'analog-glob-routes',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the route replacements.\n enforce: 'post',\n config(_config) {\n config = _config;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n },\n /**\n * Transforms code to replace ANALOG_ROUTE_FILES and ANALOG_CONTENT_ROUTE_FILES\n * placeholders with actual dynamic imports of discovered route and content files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n // Vite 8 / Rolldown filtered transform: the `filter.code` substring\n // pre-filter lets the bundler skip modules that don't contain the\n // marker. '_ROUTE_FILES' is a common substring of both\n // 'ANALOG_ROUTE_FILES' and 'ANALOG_CONTENT_ROUTE_FILES'.\n //\n // IMPORTANT: Do NOT change this to 'ANALOG_ROUTE_FILES' — that is NOT\n // a substring of 'ANALOG_CONTENT_ROUTE_FILES' (they diverge at\n // position 7: 'ANALOG_C...' vs 'ANALOG_R...'). When tsconfig path\n // aliases resolve @analogjs/router and @analogjs/router/content to\n // separate source files, each variable lives in its own module and\n // the filter must match both independently.\n transform: {\n filter: {\n code: '_ROUTE_FILES',\n },\n handler(code) {\n if (\n code.includes('ANALOG_ROUTE_FILES') ||\n code.includes('ANALOG_CONTENT_ROUTE_FILES')\n ) {\n // Discover route files using tinyglobby\n // NOTE: { absolute: true } returns absolute paths for ALL files\n const routeFiles = discoverRouteFiles();\n\n // Discover content files using tinyglobby\n const contentRouteFiles = discoverContentRouteFiles();\n\n let result = code.replace(\n 'ANALOG_ROUTE_FILES = {};',\n `\n ANALOG_ROUTE_FILES = {${routeFiles.map((module) => {\n // Keys are app-root-relative for in-app files,\n // workspace-relative for library files (additionalPagesDirs).\n // import() keeps absolute paths for Vite's module resolution.\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_ROUTE_FILES = {};',\n `\n ANALOG_CONTENT_ROUTE_FILES = {${contentRouteFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}?analog-content-file=true').then(m => m.default)`;\n })}};\n `,\n );\n\n result = result.replace(\n 'ANALOG_CONTENT_FILE_COUNT = 0',\n `ANALOG_CONTENT_FILE_COUNT = ${contentRouteFiles.length}`,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n {\n name: 'analog-glob-endpoints',\n // enforce: 'post' ensures this transform runs AFTER the Angular compiler\n // plugin, which replaces module content with its own compiled output.\n // Without this, the Angular plugin would overwrite the endpoint replacements.\n enforce: 'post',\n /**\n * Transforms code to replace ANALOG_PAGE_ENDPOINTS placeholder\n * with actual dynamic imports of discovered server endpoint files.\n *\n * @param code The source code to transform\n * @returns Transformed code with dynamic imports or undefined if no transformation needed\n */\n transform: {\n filter: {\n code: 'ANALOG_PAGE_ENDPOINTS',\n },\n handler(code) {\n if (code.includes('ANALOG_PAGE_ENDPOINTS')) {\n // Discover server endpoint files using tinyglobby\n const endpointFiles = discoverEndpointFiles();\n\n const result = code.replace(\n 'ANALOG_PAGE_ENDPOINTS = {};',\n `\n ANALOG_PAGE_ENDPOINTS = {${endpointFiles.map((module) => {\n const key = getModuleKey(module);\n return `\"${key}\": () => import('${module}')`;\n })}};\n `,\n );\n\n return {\n code: result,\n map: { mappings: '' },\n };\n }\n\n return;\n },\n },\n },\n ];\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from "vite";
2
+ import type { StylesheetDependency, StylesheetDiagnostic, StylesheetTransformContext, StylesheetTransformResult } from "./style-preprocessor.js";
3
+ export interface StylePipelineStylesheetRegistry {
4
+ getPublicIdsForSource(sourcePath: string): string[];
5
+ getRequestIdsForSource(sourcePath: string): string[];
6
+ getDependenciesForSource(sourcePath: string): StylesheetDependency[];
7
+ getDiagnosticsForSource(sourcePath: string): StylesheetDiagnostic[];
8
+ getTagsForSource(sourcePath: string): string[];
9
+ }
10
+ export interface AngularStylePipelineContext {
11
+ workspaceRoot: string;
12
+ }
13
+ export interface AngularStylePipelinePlugin {
14
+ name: string;
15
+ preprocessStylesheet?: (code: string, context: StylesheetTransformContext) => string | StylesheetTransformResult | undefined;
16
+ configureStylesheetRegistry?: (registry: StylePipelineStylesheetRegistry, context: AngularStylePipelineContext) => void;
17
+ }
18
+ export interface AngularStylePipelineOptions {
19
+ plugins: AngularStylePipelinePlugin[];
20
+ }
21
+ export declare function defineAngularStylePipeline<const T extends AngularStylePipelineOptions>(options: T): T;
22
+ export declare function defineAngularStylePipelinePlugins<const T extends AngularStylePipelinePlugin[]>(plugins: T): T;
23
+ export interface StylePipelineContext {
24
+ workspaceRoot: string;
25
+ }
26
+ export type StylePipelinePluginFactory = (context: StylePipelineContext) => Plugin | Plugin[] | false | null | undefined;
27
+ export type StylePipelinePluginEntry = Plugin | Plugin[] | StylePipelinePluginFactory | false | null | undefined;
28
+ export interface StylePipelineOptions {
29
+ plugins?: StylePipelinePluginEntry[];
30
+ angularPlugins?: AngularStylePipelinePlugin[];
31
+ }
32
+ export declare function defineStylePipeline<const T extends StylePipelineOptions>(options: T): T;
33
+ export declare function defineStylePipelinePlugins<const T extends StylePipelinePluginEntry[]>(plugins: T): T;
34
+ export declare function resolveStylePipelinePlugins(options: StylePipelineOptions | false | undefined, workspaceRoot?: string): Plugin[];
@@ -0,0 +1,32 @@
1
+ //#region packages/platform/src/lib/style-pipeline.ts
2
+ function defineAngularStylePipeline(options) {
3
+ return options;
4
+ }
5
+ function defineAngularStylePipelinePlugins(plugins) {
6
+ return plugins;
7
+ }
8
+ function defineStylePipeline(options) {
9
+ return options;
10
+ }
11
+ function defineStylePipelinePlugins(plugins) {
12
+ return plugins;
13
+ }
14
+ function resolveStylePipelinePlugins(options, workspaceRoot) {
15
+ if (!options?.plugins?.length) return [];
16
+ const context = { workspaceRoot: workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd() };
17
+ const resolved = [];
18
+ for (const entry of options.plugins) {
19
+ const plugins = typeof entry === "function" ? entry(context) : entry;
20
+ if (!plugins) continue;
21
+ if (Array.isArray(plugins)) {
22
+ resolved.push(...plugins);
23
+ continue;
24
+ }
25
+ resolved.push(plugins);
26
+ }
27
+ return resolved;
28
+ }
29
+ //#endregion
30
+ export { defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, resolveStylePipelinePlugins };
31
+
32
+ //# sourceMappingURL=style-pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-pipeline.js","names":[],"sources":["../../../src/lib/style-pipeline.ts"],"sourcesContent":["import type { Plugin } from 'vite';\nimport type {\n StylesheetDependency,\n StylesheetDiagnostic,\n StylesheetTransformContext,\n StylesheetTransformResult,\n} from './style-preprocessor.js';\n\nexport interface StylePipelineStylesheetRegistry {\n getPublicIdsForSource(sourcePath: string): string[];\n getRequestIdsForSource(sourcePath: string): string[];\n getDependenciesForSource(sourcePath: string): StylesheetDependency[];\n getDiagnosticsForSource(sourcePath: string): StylesheetDiagnostic[];\n getTagsForSource(sourcePath: string): string[];\n}\n\nexport interface AngularStylePipelineContext {\n workspaceRoot: string;\n}\n\nexport interface AngularStylePipelinePlugin {\n name: string;\n preprocessStylesheet?: (\n code: string,\n context: StylesheetTransformContext,\n ) => string | StylesheetTransformResult | undefined;\n configureStylesheetRegistry?: (\n registry: StylePipelineStylesheetRegistry,\n context: AngularStylePipelineContext,\n ) => void;\n}\n\nexport interface AngularStylePipelineOptions {\n plugins: AngularStylePipelinePlugin[];\n}\n\nexport function defineAngularStylePipeline<\n const T extends AngularStylePipelineOptions,\n>(options: T): T {\n return options;\n}\n\nexport function defineAngularStylePipelinePlugins<\n const T extends AngularStylePipelinePlugin[],\n>(plugins: T): T {\n return plugins;\n}\n\nexport interface StylePipelineContext {\n workspaceRoot: string;\n}\n\nexport type StylePipelinePluginFactory = (\n context: StylePipelineContext,\n) => Plugin | Plugin[] | false | null | undefined;\n\nexport type StylePipelinePluginEntry =\n | Plugin\n | Plugin[]\n | StylePipelinePluginFactory\n | false\n | null\n | undefined;\n\nexport interface StylePipelineOptions {\n plugins?: StylePipelinePluginEntry[];\n angularPlugins?: AngularStylePipelinePlugin[];\n}\n\nexport function defineStylePipeline<const T extends StylePipelineOptions>(\n options: T,\n): T {\n return options;\n}\n\nexport function defineStylePipelinePlugins<\n const T extends StylePipelinePluginEntry[],\n>(plugins: T): T {\n return plugins;\n}\n\nexport function resolveStylePipelinePlugins(\n options: StylePipelineOptions | false | undefined,\n workspaceRoot?: string,\n): Plugin[] {\n if (!options?.plugins?.length) {\n return [];\n }\n\n const context: StylePipelineContext = {\n workspaceRoot:\n workspaceRoot ?? process.env['NX_WORKSPACE_ROOT'] ?? process.cwd(),\n };\n\n const resolved: Plugin[] = [];\n\n for (const entry of options.plugins) {\n const plugins = typeof entry === 'function' ? entry(context) : entry;\n\n if (!plugins) {\n continue;\n }\n\n if (Array.isArray(plugins)) {\n resolved.push(...plugins);\n continue;\n }\n\n resolved.push(plugins);\n }\n\n return resolved;\n}\n"],"mappings":";AAoCA,SAAgB,2BAEd,SAAe;AACf,QAAO;;AAGT,SAAgB,kCAEd,SAAe;AACf,QAAO;;AAwBT,SAAgB,oBACd,SACG;AACH,QAAO;;AAGT,SAAgB,2BAEd,SAAe;AACf,QAAO;;AAGT,SAAgB,4BACd,SACA,eACU;AACV,KAAI,CAAC,SAAS,SAAS,OACrB,QAAO,EAAE;CAGX,MAAM,UAAgC,EACpC,eACE,iBAAiB,QAAQ,IAAI,wBAAwB,QAAQ,KAAK,EACrE;CAED,MAAM,WAAqB,EAAE;AAE7B,MAAK,MAAM,SAAS,QAAQ,SAAS;EACnC,MAAM,UAAU,OAAO,UAAU,aAAa,MAAM,QAAQ,GAAG;AAE/D,MAAI,CAAC,QACH;AAGF,MAAI,MAAM,QAAQ,QAAQ,EAAE;AAC1B,YAAS,KAAK,GAAG,QAAQ;AACzB;;AAGF,WAAS,KAAK,QAAQ;;AAGxB,QAAO"}
@@ -0,0 +1,28 @@
1
+ export interface StylesheetTransformContext {
2
+ filename: string;
3
+ containingFile?: string;
4
+ resourceFile?: string;
5
+ className?: string;
6
+ order?: number;
7
+ inline: boolean;
8
+ }
9
+ export interface StylesheetDependency {
10
+ id: string;
11
+ kind?: "file" | "virtual" | "token" | "bridge" | "manifest" | "runtime";
12
+ owner?: string;
13
+ }
14
+ export interface StylesheetDiagnostic {
15
+ severity: "warning" | "error";
16
+ code: string;
17
+ message: string;
18
+ }
19
+ export interface StylesheetTransformResult {
20
+ code: string;
21
+ dependencies?: Array<string | StylesheetDependency>;
22
+ diagnostics?: StylesheetDiagnostic[];
23
+ tags?: string[];
24
+ }
25
+ export type StylePreprocessor = (code: string, filename: string, context?: StylesheetTransformContext) => string | StylesheetTransformResult;
26
+ export declare function normalizeStylesheetTransformResult(value: string | StylesheetTransformResult | undefined, fallbackCode: string): StylesheetTransformResult;
27
+ export declare function normalizeStylesheetDependencies(dependencies: Array<string | StylesheetDependency> | undefined): StylesheetDependency[];
28
+ export declare function composeStylePreprocessors(preprocessors: Array<StylePreprocessor | false | null | undefined>): StylePreprocessor | undefined;
@@ -0,0 +1,35 @@
1
+ //#region packages/platform/src/lib/style-preprocessor.ts
2
+ function normalizeStylesheetTransformResult(value, fallbackCode) {
3
+ if (value == null) return { code: fallbackCode };
4
+ if (typeof value === "string") return { code: value };
5
+ return {
6
+ code: value.code ?? fallbackCode,
7
+ dependencies: value.dependencies ?? [],
8
+ diagnostics: value.diagnostics ?? [],
9
+ tags: value.tags ?? []
10
+ };
11
+ }
12
+ function normalizeStylesheetDependencies(dependencies) {
13
+ return (dependencies ?? []).map((dependency) => typeof dependency === "string" ? { id: dependency } : dependency);
14
+ }
15
+ function composeStylePreprocessors(preprocessors) {
16
+ const active = preprocessors.filter((preprocessor) => !!preprocessor);
17
+ if (!active.length) return;
18
+ return (code, filename, context) => {
19
+ let current = normalizeStylesheetTransformResult(void 0, code);
20
+ for (const preprocessor of active) {
21
+ const next = normalizeStylesheetTransformResult(preprocessor(current.code, filename, context), current.code);
22
+ current = {
23
+ code: next.code,
24
+ dependencies: [...current.dependencies ?? [], ...next.dependencies ?? []],
25
+ diagnostics: [...current.diagnostics ?? [], ...next.diagnostics ?? []],
26
+ tags: [...current.tags ?? [], ...next.tags ?? []]
27
+ };
28
+ }
29
+ return current;
30
+ };
31
+ }
32
+ //#endregion
33
+ export { composeStylePreprocessors, normalizeStylesheetDependencies, normalizeStylesheetTransformResult };
34
+
35
+ //# sourceMappingURL=style-preprocessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-preprocessor.js","names":[],"sources":["../../../src/lib/style-preprocessor.ts"],"sourcesContent":["export interface StylesheetTransformContext {\n filename: string;\n containingFile?: string;\n resourceFile?: string;\n className?: string;\n order?: number;\n inline: boolean;\n}\n\nexport interface StylesheetDependency {\n id: string;\n kind?: 'file' | 'virtual' | 'token' | 'bridge' | 'manifest' | 'runtime';\n owner?: string;\n}\n\nexport interface StylesheetDiagnostic {\n severity: 'warning' | 'error';\n code: string;\n message: string;\n}\n\nexport interface StylesheetTransformResult {\n code: string;\n dependencies?: Array<string | StylesheetDependency>;\n diagnostics?: StylesheetDiagnostic[];\n tags?: string[];\n}\n\nexport type StylePreprocessor = (\n code: string,\n filename: string,\n context?: StylesheetTransformContext,\n) => string | StylesheetTransformResult;\n\nexport function normalizeStylesheetTransformResult(\n value: string | StylesheetTransformResult | undefined,\n fallbackCode: string,\n): StylesheetTransformResult {\n if (value == null) {\n return { code: fallbackCode };\n }\n\n if (typeof value === 'string') {\n return { code: value };\n }\n\n return {\n code: value.code ?? fallbackCode,\n dependencies: value.dependencies ?? [],\n diagnostics: value.diagnostics ?? [],\n tags: value.tags ?? [],\n };\n}\n\nexport function normalizeStylesheetDependencies(\n dependencies: Array<string | StylesheetDependency> | undefined,\n): StylesheetDependency[] {\n return (dependencies ?? []).map((dependency) =>\n typeof dependency === 'string' ? { id: dependency } : dependency,\n );\n}\n\nexport function composeStylePreprocessors(\n preprocessors: Array<StylePreprocessor | false | null | undefined>,\n): StylePreprocessor | undefined {\n const active = preprocessors.filter(\n (preprocessor): preprocessor is StylePreprocessor => !!preprocessor,\n );\n\n if (!active.length) {\n return undefined;\n }\n\n return (code, filename, context) => {\n let current = normalizeStylesheetTransformResult(undefined, code);\n\n for (const preprocessor of active) {\n const next = normalizeStylesheetTransformResult(\n preprocessor(current.code, filename, context),\n current.code,\n );\n current = {\n code: next.code,\n dependencies: [\n ...(current.dependencies ?? []),\n ...(next.dependencies ?? []),\n ],\n diagnostics: [\n ...(current.diagnostics ?? []),\n ...(next.diagnostics ?? []),\n ],\n tags: [...(current.tags ?? []), ...(next.tags ?? [])],\n };\n }\n\n return current;\n };\n}\n"],"mappings":";AAkCA,SAAgB,mCACd,OACA,cAC2B;AAC3B,KAAI,SAAS,KACX,QAAO,EAAE,MAAM,cAAc;AAG/B,KAAI,OAAO,UAAU,SACnB,QAAO,EAAE,MAAM,OAAO;AAGxB,QAAO;EACL,MAAM,MAAM,QAAQ;EACpB,cAAc,MAAM,gBAAgB,EAAE;EACtC,aAAa,MAAM,eAAe,EAAE;EACpC,MAAM,MAAM,QAAQ,EAAE;EACvB;;AAGH,SAAgB,gCACd,cACwB;AACxB,SAAQ,gBAAgB,EAAE,EAAE,KAAK,eAC/B,OAAO,eAAe,WAAW,EAAE,IAAI,YAAY,GAAG,WACvD;;AAGH,SAAgB,0BACd,eAC+B;CAC/B,MAAM,SAAS,cAAc,QAC1B,iBAAoD,CAAC,CAAC,aACxD;AAED,KAAI,CAAC,OAAO,OACV;AAGF,SAAQ,MAAM,UAAU,YAAY;EAClC,IAAI,UAAU,mCAAmC,KAAA,GAAW,KAAK;AAEjE,OAAK,MAAM,gBAAgB,QAAQ;GACjC,MAAM,OAAO,mCACX,aAAa,QAAQ,MAAM,UAAU,QAAQ,EAC7C,QAAQ,KACT;AACD,aAAU;IACR,MAAM,KAAK;IACX,cAAc,CACZ,GAAI,QAAQ,gBAAgB,EAAE,EAC9B,GAAI,KAAK,gBAAgB,EAAE,CAC5B;IACD,aAAa,CACX,GAAI,QAAQ,eAAe,EAAE,EAC7B,GAAI,KAAK,eAAe,EAAE,CAC3B;IACD,MAAM,CAAC,GAAI,QAAQ,QAAQ,EAAE,EAAG,GAAI,KAAK,QAAQ,EAAE,CAAE;IACtD;;AAGH,SAAO"}
@@ -1,4 +1,4 @@
1
- import type { StylePreprocessor } from "@analogjs/vite-plugin-angular";
1
+ import type { StylePreprocessor } from "./style-preprocessor.js";
2
2
  export type TailwindPreprocessorMode = "auto" | "disabled" | {
3
3
  prefix: string;
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tailwind-preprocessor.js","names":[],"sources":["../../../src/lib/tailwind-preprocessor.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { StylePreprocessor } from '@analogjs/vite-plugin-angular';\nimport { debugTailwind } from './utils/debug.js';\n\nexport type TailwindPreprocessorMode = 'auto' | 'disabled' | { prefix: string };\n\nexport interface TailwindPreprocessorOptions {\n /** Absolute path to the Tailwind root CSS file that imports `tailwindcss`. */\n tailwindRootCss: string;\n\n /**\n * Controls whether the preprocessor auto-detects, disables, or manually\n * overrides the Tailwind prefix for a given file.\n */\n mode?:\n | TailwindPreprocessorMode\n | ((filename: string) => TailwindPreprocessorMode);\n\n /**\n * Optional predicate to override the default `@reference` injection behavior.\n */\n shouldInject?: (\n code: string,\n filename: string,\n resolvedPrefix: string | null,\n ) => boolean;\n}\n\n/**\n * Creates a stylesheet preprocessor that injects Tailwind v4 `@reference`\n * directives into Angular component styles when needed. The Tailwind prefix is\n * detected from the configured root CSS file.\n */\nexport function tailwindPreprocessor(\n options: TailwindPreprocessorOptions,\n): StylePreprocessor {\n const { tailwindRootCss, mode: modeOption = 'auto', shouldInject } = options;\n let rootPrefix: string | undefined;\n\n debugTailwind('configured', { tailwindRootCss, mode: modeOption });\n\n return (code: string, filename: string): string => {\n if (code.includes('@reference')) {\n debugTailwind('skip (already has @reference)', { filename });\n return code;\n }\n\n const resolvedMode =\n typeof modeOption === 'function' ? modeOption(filename) : modeOption;\n\n if (resolvedMode === 'disabled') {\n debugTailwind('skip (mode disabled)', { filename });\n return code;\n }\n\n const resolvedPrefix =\n typeof resolvedMode === 'object' ? resolvedMode.prefix : getRootPrefix();\n const isRootFile =\n path.resolve(filename) === path.resolve(tailwindRootCss) ||\n /@import\\s+[\"']tailwindcss[\"']/.test(code);\n const hasTailwindUsage = resolvedPrefix\n ? code.includes(`${resolvedPrefix}:`)\n : false;\n const shouldAddReference = shouldInject\n ? shouldInject(code, filename, resolvedPrefix)\n : hasTailwindUsage && !isRootFile;\n\n if (!shouldAddReference || !resolvedPrefix) {\n debugTailwind('skip (no injection needed)', {\n filename,\n resolvedPrefix,\n isRootFile,\n hasTailwindUsage,\n });\n return code;\n }\n\n const refPath = path\n .relative(path.dirname(filename), tailwindRootCss)\n .replace(/\\\\/g, '/');\n debugTailwind('injected @reference', { filename, refPath });\n\n return `@reference \"${refPath}\";\\n${code}`;\n };\n\n function getRootPrefix(): string | null {\n if (rootPrefix === undefined) {\n rootPrefix = extractTailwindPrefix(\n readFileSync(tailwindRootCss, 'utf-8'),\n );\n }\n\n return rootPrefix;\n }\n}\n\nfunction extractTailwindPrefix(code: string): string | null {\n const prefixMatch = code.match(\n /@import\\s+[\"']tailwindcss[\"']\\s+prefix\\(\\s*([^)\\s;]+)\\s*\\)/i,\n );\n\n return prefixMatch?.[1]?.trim() ?? null;\n}\n"],"mappings":";;;;;;;;;AAmCA,SAAgB,qBACd,SACmB;CACnB,MAAM,EAAE,iBAAiB,MAAM,aAAa,QAAQ,iBAAiB;CACrE,IAAI;AAEJ,eAAc,cAAc;EAAE;EAAiB,MAAM;EAAY,CAAC;AAElE,SAAQ,MAAc,aAA6B;AACjD,MAAI,KAAK,SAAS,aAAa,EAAE;AAC/B,iBAAc,iCAAiC,EAAE,UAAU,CAAC;AAC5D,UAAO;;EAGT,MAAM,eACJ,OAAO,eAAe,aAAa,WAAW,SAAS,GAAG;AAE5D,MAAI,iBAAiB,YAAY;AAC/B,iBAAc,wBAAwB,EAAE,UAAU,CAAC;AACnD,UAAO;;EAGT,MAAM,iBACJ,OAAO,iBAAiB,WAAW,aAAa,SAAS,eAAe;EAC1E,MAAM,aACJ,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,gBAAgB,IACxD,gCAAgC,KAAK,KAAK;EAC5C,MAAM,mBAAmB,iBACrB,KAAK,SAAS,GAAG,eAAe,GAAG,GACnC;AAKJ,MAAI,EAJuB,eACvB,aAAa,MAAM,UAAU,eAAe,GAC5C,oBAAoB,CAAC,eAEE,CAAC,gBAAgB;AAC1C,iBAAc,8BAA8B;IAC1C;IACA;IACA;IACA;IACD,CAAC;AACF,UAAO;;EAGT,MAAM,UAAU,KACb,SAAS,KAAK,QAAQ,SAAS,EAAE,gBAAgB,CACjD,QAAQ,OAAO,IAAI;AACtB,gBAAc,uBAAuB;GAAE;GAAU;GAAS,CAAC;AAE3D,SAAO,eAAe,QAAQ,MAAM;;CAGtC,SAAS,gBAA+B;AACtC,MAAI,eAAe,KAAA,EACjB,cAAa,sBACX,aAAa,iBAAiB,QAAQ,CACvC;AAGH,SAAO;;;AAIX,SAAS,sBAAsB,MAA6B;AAK1D,QAJoB,KAAK,MACvB,8DACD,GAEoB,IAAI,MAAM,IAAI"}
1
+ {"version":3,"file":"tailwind-preprocessor.js","names":[],"sources":["../../../src/lib/tailwind-preprocessor.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport path from 'node:path';\n\nimport type { StylePreprocessor } from './style-preprocessor.js';\nimport { debugTailwind } from './utils/debug.js';\n\nexport type TailwindPreprocessorMode = 'auto' | 'disabled' | { prefix: string };\n\nexport interface TailwindPreprocessorOptions {\n /** Absolute path to the Tailwind root CSS file that imports `tailwindcss`. */\n tailwindRootCss: string;\n\n /**\n * Controls whether the preprocessor auto-detects, disables, or manually\n * overrides the Tailwind prefix for a given file.\n */\n mode?:\n | TailwindPreprocessorMode\n | ((filename: string) => TailwindPreprocessorMode);\n\n /**\n * Optional predicate to override the default `@reference` injection behavior.\n */\n shouldInject?: (\n code: string,\n filename: string,\n resolvedPrefix: string | null,\n ) => boolean;\n}\n\n/**\n * Creates a stylesheet preprocessor that injects Tailwind v4 `@reference`\n * directives into Angular component styles when needed. The Tailwind prefix is\n * detected from the configured root CSS file.\n */\nexport function tailwindPreprocessor(\n options: TailwindPreprocessorOptions,\n): StylePreprocessor {\n const { tailwindRootCss, mode: modeOption = 'auto', shouldInject } = options;\n let rootPrefix: string | undefined;\n\n debugTailwind('configured', { tailwindRootCss, mode: modeOption });\n\n return (code: string, filename: string): string => {\n if (code.includes('@reference')) {\n debugTailwind('skip (already has @reference)', { filename });\n return code;\n }\n\n const resolvedMode =\n typeof modeOption === 'function' ? modeOption(filename) : modeOption;\n\n if (resolvedMode === 'disabled') {\n debugTailwind('skip (mode disabled)', { filename });\n return code;\n }\n\n const resolvedPrefix =\n typeof resolvedMode === 'object' ? resolvedMode.prefix : getRootPrefix();\n const isRootFile =\n path.resolve(filename) === path.resolve(tailwindRootCss) ||\n /@import\\s+[\"']tailwindcss[\"']/.test(code);\n const hasTailwindUsage = resolvedPrefix\n ? code.includes(`${resolvedPrefix}:`)\n : false;\n const shouldAddReference = shouldInject\n ? shouldInject(code, filename, resolvedPrefix)\n : hasTailwindUsage && !isRootFile;\n\n if (!shouldAddReference || !resolvedPrefix) {\n debugTailwind('skip (no injection needed)', {\n filename,\n resolvedPrefix,\n isRootFile,\n hasTailwindUsage,\n });\n return code;\n }\n\n const refPath = path\n .relative(path.dirname(filename), tailwindRootCss)\n .replace(/\\\\/g, '/');\n debugTailwind('injected @reference', { filename, refPath });\n\n return `@reference \"${refPath}\";\\n${code}`;\n };\n\n function getRootPrefix(): string | null {\n if (rootPrefix === undefined) {\n rootPrefix = extractTailwindPrefix(\n readFileSync(tailwindRootCss, 'utf-8'),\n );\n }\n\n return rootPrefix;\n }\n}\n\nfunction extractTailwindPrefix(code: string): string | null {\n const prefixMatch = code.match(\n /@import\\s+[\"']tailwindcss[\"']\\s+prefix\\(\\s*([^)\\s;]+)\\s*\\)/i,\n );\n\n return prefixMatch?.[1]?.trim() ?? null;\n}\n"],"mappings":";;;;;;;;;AAmCA,SAAgB,qBACd,SACmB;CACnB,MAAM,EAAE,iBAAiB,MAAM,aAAa,QAAQ,iBAAiB;CACrE,IAAI;AAEJ,eAAc,cAAc;EAAE;EAAiB,MAAM;EAAY,CAAC;AAElE,SAAQ,MAAc,aAA6B;AACjD,MAAI,KAAK,SAAS,aAAa,EAAE;AAC/B,iBAAc,iCAAiC,EAAE,UAAU,CAAC;AAC5D,UAAO;;EAGT,MAAM,eACJ,OAAO,eAAe,aAAa,WAAW,SAAS,GAAG;AAE5D,MAAI,iBAAiB,YAAY;AAC/B,iBAAc,wBAAwB,EAAE,UAAU,CAAC;AACnD,UAAO;;EAGT,MAAM,iBACJ,OAAO,iBAAiB,WAAW,aAAa,SAAS,eAAe;EAC1E,MAAM,aACJ,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,gBAAgB,IACxD,gCAAgC,KAAK,KAAK;EAC5C,MAAM,mBAAmB,iBACrB,KAAK,SAAS,GAAG,eAAe,GAAG,GACnC;AAKJ,MAAI,EAJuB,eACvB,aAAa,MAAM,UAAU,eAAe,GAC5C,oBAAoB,CAAC,eAEE,CAAC,gBAAgB;AAC1C,iBAAc,8BAA8B;IAC1C;IACA;IACA;IACA;IACD,CAAC;AACF,UAAO;;EAGT,MAAM,UAAU,KACb,SAAS,KAAK,QAAQ,SAAS,EAAE,gBAAgB,CACjD,QAAQ,OAAO,IAAI;AACtB,gBAAc,uBAAuB;GAAE;GAAU;GAAS,CAAC;AAE3D,SAAO,eAAe,QAAQ,MAAM;;CAGtC,SAAS,gBAA+B;AACtC,MAAI,eAAe,KAAA,EACjB,cAAa,sBACX,aAAa,iBAAiB,QAAQ,CACvC;AAGH,SAAO;;;AAIX,SAAS,sBAAsB,MAA6B;AAK1D,QAJoB,KAAK,MACvB,8DACD,GAEoB,IAAI,MAAM,IAAI"}
@@ -3,7 +3,8 @@ export declare const debugRoutes: unknown;
3
3
  export declare const debugContent: unknown;
4
4
  export declare const debugTypedRouter: unknown;
5
5
  export declare const debugTailwind: unknown;
6
- export type DebugScope = "analog:*" | "analog:platform" | "analog:platform:*" | "analog:platform:routes" | "analog:platform:content" | "analog:platform:typed-router" | "analog:platform:tailwind" | "analog:angular:*" | "analog:angular:hmr" | "analog:angular:styles" | "analog:angular:compiler" | "analog:angular:compilation-api" | "analog:angular:tailwind" | "analog:nitro" | "analog:nitro:*" | "analog:nitro:ssr" | "analog:nitro:prerender" | (string & {});
6
+ export declare const debugStylePipeline: unknown;
7
+ export type DebugScope = "analog:*" | "analog:platform" | "analog:platform:*" | "analog:platform:routes" | "analog:platform:content" | "analog:platform:typed-router" | "analog:platform:tailwind" | "analog:platform:style-pipeline" | "analog:angular:*" | "analog:angular:hmr" | "analog:angular:styles" | "analog:angular:compiler" | "analog:angular:compilation-api" | "analog:angular:tailwind" | "analog:angular:style-pipeline" | "analog:nitro" | "analog:nitro:*" | "analog:nitro:ssr" | "analog:nitro:prerender" | (string & {});
7
8
  export type DebugMode = "build" | "dev";
8
9
  export interface DebugModeOptions {
9
10
  scopes?: boolean | DebugScope[];
@@ -14,7 +14,8 @@ var harness = createDebugHarness({
14
14
  debugRoutes,
15
15
  debugContent,
16
16
  debugTypedRouter,
17
- debugTailwind
17
+ debugTailwind,
18
+ createDebug("analog:platform:style-pipeline")
18
19
  ], debugInstances]
19
20
  });
20
21
  var applyDebugOption = harness.applyDebugOption;
@@ -1 +1 @@
1
- {"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { debugInstances as nitroDebugInstances } from '@analogjs/vite-plugin-nitro/internal';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\nexport const debugTailwind = createDebug('analog:platform:tailwind');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n debugTailwind,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:platform:tailwind'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:tailwind'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;AAC3D,IAAa,cAAc,YAAY,yBAAyB;AAChE,IAAa,eAAe,YAAY,0BAA0B;AAClE,IAAa,mBAAmB,YAAY,+BAA+B;AAC3E,IAAa,gBAAgB,YAAY,2BAA2B;AAiDpE,IAAM,UAAU,mBAAmB;CACjC,mBAAmB;CACnB,gBAAgB,CAjDa;EAC7B;EACA;EACA;EACA;EACA;EACD,EA2C0C,eAAoB;CAC9D,CAAC;AAEF,IAAa,mBAGD,QAAQ;AACpB,IAAa,wBACX,QAAQ;AACqC,QAAQ"}
1
+ {"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { debugInstances as nitroDebugInstances } from '@analogjs/vite-plugin-nitro/internal';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\nexport const debugTailwind = createDebug('analog:platform:tailwind');\nexport const debugStylePipeline = createDebug('analog:platform:style-pipeline');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n debugTailwind,\n debugStylePipeline,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:platform:tailwind'\n | 'analog:platform:style-pipeline'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:tailwind'\n | 'analog:angular:style-pipeline'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;AAC3D,IAAa,cAAc,YAAY,yBAAyB;AAChE,IAAa,eAAe,YAAY,0BAA0B;AAClE,IAAa,mBAAmB,YAAY,+BAA+B;AAC3E,IAAa,gBAAgB,YAAY,2BAA2B;AAqDpE,IAAM,UAAU,mBAAmB;CACjC,mBAAmB;CACnB,gBAAgB,CApDa;EAC7B;EACA;EACA;EACA;EACA;EAPgC,YAAY,iCAAiC;EAS9E,EA6C0C,eAAoB;CAC9D,CAAC;AAEF,IAAa,mBAGD,QAAQ;AACpB,IAAa,wBACX,QAAQ;AACqC,QAAQ"}
@@ -0,0 +1 @@
1
+ export * from "./lib/style-pipeline.js";
@@ -0,0 +1,2 @@
1
+ import { defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, resolveStylePipelinePlugins } from "./lib/style-pipeline.js";
2
+ export { defineAngularStylePipeline, defineAngularStylePipelinePlugins, defineStylePipeline, defineStylePipelinePlugins, resolveStylePipelinePlugins };
@@ -0,0 +1 @@
1
+ export * from "./lib/style-preprocessor.js";
@@ -0,0 +1,2 @@
1
+ import { composeStylePreprocessors, normalizeStylesheetDependencies, normalizeStylesheetTransformResult } from "./lib/style-preprocessor.js";
2
+ export { composeStylePreprocessors, normalizeStylesheetDependencies, normalizeStylesheetTransformResult };