@knighted/css 1.0.0-rc.10 → 1.0.0-rc.12

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.
@@ -16,7 +16,6 @@ const node_module_type_1 = require("node-module-type");
16
16
  const get_tsconfig_1 = require("get-tsconfig");
17
17
  const tsconfig_paths_1 = require("tsconfig-paths");
18
18
  const css_js_1 = require("./css.cjs");
19
- const loaderInternals_js_1 = require("./loaderInternals.cjs");
20
19
  const stableSelectorsLiteral_js_1 = require("./stableSelectorsLiteral.cjs");
21
20
  const stableNamespace_js_1 = require("./stableNamespace.cjs");
22
21
  let activeCssWithMeta = css_js_1.cssWithMeta;
@@ -65,22 +64,19 @@ function getImportMetaUrl() {
65
64
  }
66
65
  }
67
66
  const PACKAGE_ROOT = resolvePackageRoot();
68
- const DEFAULT_TYPES_ROOT = node_path_1.default.join(PACKAGE_ROOT, 'types-stub');
69
- const DEFAULT_OUT_DIR = node_path_1.default.join(PACKAGE_ROOT, 'node_modules', '.knighted-css');
67
+ const SELECTOR_REFERENCE = '.knighted-css';
68
+ const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
70
69
  async function generateTypes(options = {}) {
71
70
  const rootDir = node_path_1.default.resolve(options.rootDir ?? process.cwd());
72
71
  const include = normalizeIncludeOptions(options.include, rootDir);
73
- const outDir = node_path_1.default.resolve(options.outDir ?? DEFAULT_OUT_DIR);
74
- const typesRoot = node_path_1.default.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT);
72
+ const cacheDir = node_path_1.default.resolve(options.outDir ?? node_path_1.default.join(rootDir, '.knighted-css'));
75
73
  const tsconfig = loadTsconfigResolutionContext(rootDir);
76
74
  await es_module_lexer_1.init;
77
- await promises_1.default.mkdir(outDir, { recursive: true });
78
- await promises_1.default.mkdir(typesRoot, { recursive: true });
75
+ await promises_1.default.mkdir(cacheDir, { recursive: true });
79
76
  const internalOptions = {
80
77
  rootDir,
81
78
  include,
82
- outDir,
83
- typesRoot,
79
+ cacheDir,
84
80
  stableNamespace: options.stableNamespace,
85
81
  tsconfig,
86
82
  };
@@ -89,32 +85,27 @@ async function generateTypes(options = {}) {
89
85
  async function generateDeclarations(options) {
90
86
  const peerResolver = createProjectPeerResolver(options.rootDir);
91
87
  const files = await collectCandidateFiles(options.include);
92
- const manifestPath = node_path_1.default.join(options.outDir, 'manifest.json');
93
- const previousManifest = await readManifest(manifestPath);
94
- const nextManifest = {};
88
+ const selectorModulesManifestPath = node_path_1.default.join(options.cacheDir, 'selector-modules.json');
89
+ const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
90
+ const nextSelectorManifest = {};
95
91
  const selectorCache = new Map();
96
- const processedSpecifiers = new Set();
97
- const declarations = [];
92
+ const processedSelectors = new Set();
98
93
  const warnings = [];
99
- let writes = 0;
94
+ let selectorModuleWrites = 0;
100
95
  for (const filePath of files) {
101
96
  const matches = await findSpecifierImports(filePath);
102
97
  for (const match of matches) {
103
98
  const cleaned = match.specifier.trim();
104
99
  const inlineFree = stripInlineLoader(cleaned);
105
- if (!inlineFree.includes('?knighted-css'))
106
- continue;
107
- const { resource, query } = splitResourceAndQuery(inlineFree);
108
- if (!query || !(0, loaderInternals_js_1.hasQueryFlag)(query, loaderInternals_js_1.TYPES_QUERY_FLAG)) {
109
- continue;
110
- }
111
- if (processedSpecifiers.has(cleaned)) {
100
+ const { resource } = splitResourceAndQuery(inlineFree);
101
+ const selectorSource = extractSelectorSourceSpecifier(resource);
102
+ if (!selectorSource) {
112
103
  continue;
113
104
  }
114
105
  const resolvedNamespace = (0, stableNamespace_js_1.resolveStableNamespace)(options.stableNamespace);
115
- const resolvedPath = await resolveImportPath(resource, match.importer, options.rootDir, options.tsconfig);
106
+ const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig);
116
107
  if (!resolvedPath) {
117
- warnings.push(`Unable to resolve ${resource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
108
+ warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
118
109
  continue;
119
110
  }
120
111
  const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
@@ -138,38 +129,28 @@ async function generateDeclarations(options) {
138
129
  }
139
130
  selectorCache.set(cacheKey, selectorMap);
140
131
  }
141
- const variant = (0, loaderInternals_js_1.determineSelectorVariant)(query);
142
- const declaration = formatModuleDeclaration(cleaned, variant, selectorMap);
143
- const declarationHash = hashContent(declaration);
144
- const fileName = buildDeclarationFileName(cleaned);
145
- const targetPath = node_path_1.default.join(options.outDir, fileName);
146
- const previousEntry = previousManifest[cleaned];
147
- const needsWrite = previousEntry?.hash !== declarationHash || !(await fileExists(targetPath));
148
- if (needsWrite) {
149
- await promises_1.default.writeFile(targetPath, declaration, 'utf8');
150
- writes += 1;
132
+ if (!isWithinRoot(resolvedPath, options.rootDir)) {
133
+ warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
134
+ continue;
135
+ }
136
+ const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
137
+ if (processedSelectors.has(manifestKey)) {
138
+ continue;
151
139
  }
152
- nextManifest[cleaned] = { file: fileName, hash: declarationHash };
153
- if (needsWrite) {
154
- declarations.push({ specifier: cleaned, filePath: targetPath });
140
+ const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest);
141
+ if (moduleWrite) {
142
+ selectorModuleWrites += 1;
155
143
  }
156
- processedSpecifiers.add(cleaned);
144
+ processedSelectors.add(manifestKey);
157
145
  }
158
146
  }
159
- const removed = await removeStaleDeclarations(previousManifest, nextManifest, options.outDir);
160
- await writeManifest(manifestPath, nextManifest);
161
- const typesIndexPath = node_path_1.default.join(options.typesRoot, 'index.d.ts');
162
- await writeTypesIndex(typesIndexPath, nextManifest, options.outDir);
163
- if (Object.keys(nextManifest).length === 0) {
164
- declarations.length = 0;
165
- }
147
+ const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
148
+ await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
166
149
  return {
167
- written: writes,
168
- removed,
169
- declarations,
150
+ selectorModulesWritten: selectorModuleWrites,
151
+ selectorModulesRemoved,
170
152
  warnings,
171
- outDir: options.outDir,
172
- typesIndexPath,
153
+ manifestPath: selectorModulesManifestPath,
173
154
  };
174
155
  }
175
156
  function normalizeIncludeOptions(include, rootDir) {
@@ -227,18 +208,18 @@ async function findSpecifierImports(filePath) {
227
208
  catch {
228
209
  return [];
229
210
  }
230
- if (!source.includes('?knighted-css')) {
211
+ if (!source.includes(SELECTOR_REFERENCE)) {
231
212
  return [];
232
213
  }
233
214
  const matches = [];
234
215
  const [imports] = (0, es_module_lexer_1.parse)(source, filePath);
235
216
  for (const record of imports) {
236
217
  const specifier = record.n ?? source.slice(record.s, record.e);
237
- if (specifier && specifier.includes('?knighted-css')) {
218
+ if (specifier && specifier.includes(SELECTOR_REFERENCE)) {
238
219
  matches.push({ specifier, importer: filePath });
239
220
  }
240
221
  }
241
- const requireRegex = /require\((['"])([^'"`]+?\?knighted-css[^'"`]*)\1\)/g;
222
+ const requireRegex = /require\((['"])([^'"`]+?\.knighted-css[^'"`]*)\1\)/g;
242
223
  let reqMatch;
243
224
  while ((reqMatch = requireRegex.exec(source)) !== null) {
244
225
  const spec = reqMatch[2];
@@ -261,6 +242,21 @@ function splitResourceAndQuery(specifier) {
261
242
  }
262
243
  return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) };
263
244
  }
245
+ function extractSelectorSourceSpecifier(specifier) {
246
+ const markerIndex = specifier.indexOf(SELECTOR_REFERENCE);
247
+ if (markerIndex < 0) {
248
+ return undefined;
249
+ }
250
+ const suffix = specifier.slice(markerIndex + SELECTOR_REFERENCE.length);
251
+ if (suffix.length > 0 && !/\.(?:[cm]?[tj]s|[tj]sx)$/.test(suffix)) {
252
+ return undefined;
253
+ }
254
+ const base = specifier.slice(0, markerIndex);
255
+ if (!base) {
256
+ return undefined;
257
+ }
258
+ return base;
259
+ }
264
260
  const projectRequireCache = new Map();
265
261
  async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tsconfig) {
266
262
  if (!resourceSpecifier)
@@ -283,47 +279,29 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tscon
283
279
  return undefined;
284
280
  }
285
281
  }
286
- function buildDeclarationFileName(specifier) {
287
- const digest = node_crypto_1.default.createHash('sha1').update(specifier).digest('hex').slice(0, 12);
288
- return `knt-${digest}.d.ts`;
289
- }
290
- function formatModuleDeclaration(specifier, variant, selectors) {
291
- const literalSpecifier = JSON.stringify(specifier);
292
- const selectorType = formatSelectorType(selectors);
293
- const header = `declare module ${literalSpecifier} {`;
294
- const footer = '}';
295
- if (variant === 'types') {
296
- return `${header}
297
- export const knightedCss: string
298
- export const stableSelectors: ${selectorType}
299
- ${footer}
300
- `;
301
- }
302
- const stableLine = ` export const stableSelectors: ${selectorType}`;
303
- const shared = ` const combined: KnightedCssCombinedModule<Record<string, unknown>>
304
- export const knightedCss: string
305
- ${stableLine}`;
306
- if (variant === 'combined') {
307
- return `${header}
308
- ${shared}
309
- export default combined
310
- ${footer}
311
- `;
312
- }
313
- return `${header}
314
- ${shared}
315
- ${footer}
316
- `;
282
+ function buildSelectorModuleManifestKey(resolvedPath) {
283
+ return resolvedPath.split(node_path_1.default.sep).join('/');
317
284
  }
318
- function formatSelectorType(selectors) {
319
- if (selectors.size === 0) {
320
- return 'Readonly<Record<string, string>>';
321
- }
285
+ function buildSelectorModulePath(resolvedPath) {
286
+ return `${resolvedPath}${SELECTOR_MODULE_SUFFIX}`;
287
+ }
288
+ function formatSelectorModuleSource(selectors) {
289
+ const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
322
290
  const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
323
- const lines = entries.map(([token, selector]) => ` readonly ${JSON.stringify(token)}: ${JSON.stringify(selector)}`);
324
- return `Readonly<{
291
+ const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
292
+ const literal = lines.length > 0
293
+ ? `{
325
294
  ${lines.join('\n')}
326
- }>`;
295
+ } as const`
296
+ : '{} as const';
297
+ return `${header}
298
+ export const stableSelectors = ${literal}
299
+
300
+ export type KnightedCssStableSelectors = typeof stableSelectors
301
+ export type KnightedCssStableSelectorToken = keyof typeof stableSelectors
302
+
303
+ export default stableSelectors
304
+ `;
327
305
  }
328
306
  function hashContent(content) {
329
307
  return node_crypto_1.default.createHash('sha1').update(content).digest('hex');
@@ -340,13 +318,12 @@ async function readManifest(manifestPath) {
340
318
  async function writeManifest(manifestPath, manifest) {
341
319
  await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
342
320
  }
343
- async function removeStaleDeclarations(previous, next, outDir) {
344
- const stale = Object.entries(previous).filter(([specifier]) => !next[specifier]);
321
+ async function removeStaleSelectorModules(previous, next) {
322
+ const stale = Object.entries(previous).filter(([key]) => !next[key]);
345
323
  let removed = 0;
346
324
  for (const [, entry] of stale) {
347
- const targetPath = node_path_1.default.join(outDir, entry.file);
348
325
  try {
349
- await promises_1.default.unlink(targetPath);
326
+ await promises_1.default.unlink(entry.file);
350
327
  removed += 1;
351
328
  }
352
329
  catch {
@@ -355,25 +332,6 @@ async function removeStaleDeclarations(previous, next, outDir) {
355
332
  }
356
333
  return removed;
357
334
  }
358
- async function writeTypesIndex(indexPath, manifest, outDir) {
359
- const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
360
- const references = Object.values(manifest)
361
- .sort((a, b) => a.file.localeCompare(b.file))
362
- .map(entry => {
363
- const rel = node_path_1.default
364
- .relative(node_path_1.default.dirname(indexPath), node_path_1.default.join(outDir, entry.file))
365
- .split(node_path_1.default.sep)
366
- .join('/');
367
- return `/// <reference path="${rel}" />`;
368
- });
369
- const content = references.length > 0
370
- ? `${header}
371
- ${references.join('\n')}
372
- `
373
- : `${header}
374
- `;
375
- await promises_1.default.writeFile(indexPath, content, 'utf8');
376
- }
377
335
  function formatErrorMessage(error) {
378
336
  if (error instanceof Error && typeof error.message === 'string') {
379
337
  return error.message;
@@ -383,6 +341,23 @@ function formatErrorMessage(error) {
383
341
  function relativeToRoot(filePath, rootDir) {
384
342
  return node_path_1.default.relative(rootDir, filePath) || filePath;
385
343
  }
344
+ function isWithinRoot(filePath, rootDir) {
345
+ const relative = node_path_1.default.relative(rootDir, filePath);
346
+ return relative === '' || (!relative.startsWith('..') && !node_path_1.default.isAbsolute(relative));
347
+ }
348
+ async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest) {
349
+ const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
350
+ const targetPath = buildSelectorModulePath(resolvedPath);
351
+ const source = formatSelectorModuleSource(selectors);
352
+ const hash = hashContent(source);
353
+ const previousEntry = previousManifest[manifestKey];
354
+ const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
355
+ if (needsWrite) {
356
+ await promises_1.default.writeFile(targetPath, source, 'utf8');
357
+ }
358
+ nextManifest[manifestKey] = { file: targetPath, hash };
359
+ return needsWrite;
360
+ }
386
361
  async function fileExists(target) {
387
362
  try {
388
363
  await promises_1.default.access(target);
@@ -506,7 +481,6 @@ async function runGenerateTypesCli(argv = process.argv.slice(2)) {
506
481
  rootDir: parsed.rootDir,
507
482
  include: parsed.include,
508
483
  outDir: parsed.outDir,
509
- typesRoot: parsed.typesRoot,
510
484
  stableNamespace: parsed.stableNamespace,
511
485
  });
512
486
  reportCliResult(result);
@@ -521,12 +495,11 @@ function parseCliArgs(argv) {
521
495
  let rootDir = process.cwd();
522
496
  const include = [];
523
497
  let outDir;
524
- let typesRoot;
525
498
  let stableNamespace;
526
499
  for (let i = 0; i < argv.length; i += 1) {
527
500
  const arg = argv[i];
528
501
  if (arg === '--help' || arg === '-h') {
529
- return { rootDir, include, outDir, typesRoot, stableNamespace, help: true };
502
+ return { rootDir, include, outDir, stableNamespace, help: true };
530
503
  }
531
504
  if (arg === '--root' || arg === '-r') {
532
505
  const value = argv[++i];
@@ -552,14 +525,6 @@ function parseCliArgs(argv) {
552
525
  outDir = value;
553
526
  continue;
554
527
  }
555
- if (arg === '--types-root') {
556
- const value = argv[++i];
557
- if (!value) {
558
- throw new Error('Missing value for --types-root');
559
- }
560
- typesRoot = value;
561
- continue;
562
- }
563
528
  if (arg === '--stable-namespace') {
564
529
  const value = argv[++i];
565
530
  if (!value) {
@@ -573,7 +538,7 @@ function parseCliArgs(argv) {
573
538
  }
574
539
  include.push(arg);
575
540
  }
576
- return { rootDir, include, outDir, typesRoot, stableNamespace };
541
+ return { rootDir, include, outDir, stableNamespace };
577
542
  }
578
543
  function printHelp() {
579
544
  console.log(`Usage: knighted-css-generate-types [options]
@@ -581,20 +546,19 @@ function printHelp() {
581
546
  Options:
582
547
  -r, --root <path> Project root directory (default: cwd)
583
548
  -i, --include <path> Additional directories/files to scan (repeatable)
584
- --out-dir <path> Output directory for generated declarations
585
- --types-root <path> Directory for generated @types entrypoint
549
+ --out-dir <path> Directory to store selector module manifest cache
586
550
  --stable-namespace <name> Stable namespace prefix for generated selector maps
587
551
  -h, --help Show this help message
588
552
  `);
589
553
  }
590
554
  function reportCliResult(result) {
591
- if (result.written === 0 && result.removed === 0) {
592
- console.log('[knighted-css] No changes to ?knighted-css&types declarations (cache is up to date).');
555
+ if (result.selectorModulesWritten === 0 && result.selectorModulesRemoved === 0) {
556
+ console.log('[knighted-css] Selector modules are up to date.');
593
557
  }
594
558
  else {
595
- console.log(`[knighted-css] Updated ${result.written} declaration(s), removed ${result.removed}, output in ${result.outDir}.`);
559
+ console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
596
560
  }
597
- console.log(`[knighted-css] Type references: ${result.typesIndexPath}`);
561
+ console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
598
562
  for (const warning of result.warnings) {
599
563
  console.warn(`[knighted-css] ${warning}`);
600
564
  }
@@ -609,15 +573,12 @@ function setImportMetaUrlProvider(provider) {
609
573
  importMetaUrlProvider = provider ?? getImportMetaUrl;
610
574
  }
611
575
  exports.__generateTypesInternals = {
612
- writeTypesIndex,
613
576
  stripInlineLoader,
614
577
  splitResourceAndQuery,
578
+ extractSelectorSourceSpecifier,
615
579
  findSpecifierImports,
616
580
  resolveImportPath,
617
581
  resolvePackageRoot,
618
- buildDeclarationFileName,
619
- formatModuleDeclaration,
620
- formatSelectorType,
621
582
  relativeToRoot,
622
583
  collectCandidateFiles,
623
584
  normalizeIncludeOptions,
@@ -633,4 +594,11 @@ exports.__generateTypesInternals = {
633
594
  parseCliArgs,
634
595
  printHelp,
635
596
  reportCliResult,
597
+ buildSelectorModuleManifestKey,
598
+ buildSelectorModulePath,
599
+ formatSelectorModuleSource,
600
+ ensureSelectorModule,
601
+ removeStaleSelectorModules,
602
+ readManifest,
603
+ writeManifest,
636
604
  };
@@ -3,38 +3,30 @@ import { moduleType } from 'node-module-type';
3
3
  import { getTsconfig } from 'get-tsconfig';
4
4
  import { type MatchPath } from 'tsconfig-paths';
5
5
  import { cssWithMeta } from './css.cjs';
6
- import { type SelectorTypeVariant } from './loaderInternals.cjs';
7
- interface ManifestEntry {
8
- file: string;
9
- hash: string;
10
- }
11
- type Manifest = Record<string, ManifestEntry>;
12
6
  interface ImportMatch {
13
7
  specifier: string;
14
8
  importer: string;
15
9
  }
16
- interface DeclarationRecord {
17
- specifier: string;
18
- filePath: string;
10
+ interface ManifestEntry {
11
+ file: string;
12
+ hash: string;
19
13
  }
14
+ type SelectorModuleManifest = Record<string, ManifestEntry>;
20
15
  interface TsconfigResolutionContext {
21
16
  absoluteBaseUrl?: string;
22
17
  matchPath?: MatchPath;
23
18
  }
24
19
  type CssWithMetaFn = typeof cssWithMeta;
25
20
  export interface GenerateTypesResult {
26
- written: number;
27
- removed: number;
28
- declarations: DeclarationRecord[];
21
+ selectorModulesWritten: number;
22
+ selectorModulesRemoved: number;
29
23
  warnings: string[];
30
- outDir: string;
31
- typesIndexPath: string;
24
+ manifestPath: string;
32
25
  }
33
26
  export interface GenerateTypesOptions {
34
27
  rootDir?: string;
35
28
  include?: string[];
36
29
  outDir?: string;
37
- typesRoot?: string;
38
30
  stableNamespace?: string;
39
31
  }
40
32
  type ModuleTypeDetector = () => ReturnType<typeof moduleType>;
@@ -48,12 +40,16 @@ declare function splitResourceAndQuery(specifier: string): {
48
40
  resource: string;
49
41
  query: string;
50
42
  };
43
+ declare function extractSelectorSourceSpecifier(specifier: string): string | undefined;
51
44
  declare function resolveImportPath(resourceSpecifier: string, importerPath: string, rootDir: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
52
- declare function buildDeclarationFileName(specifier: string): string;
53
- declare function formatModuleDeclaration(specifier: string, variant: SelectorTypeVariant, selectors: Map<string, string>): string;
54
- declare function formatSelectorType(selectors: Map<string, string>): string;
55
- declare function writeTypesIndex(indexPath: string, manifest: Manifest, outDir: string): Promise<void>;
45
+ declare function buildSelectorModuleManifestKey(resolvedPath: string): string;
46
+ declare function buildSelectorModulePath(resolvedPath: string): string;
47
+ declare function formatSelectorModuleSource(selectors: Map<string, string>): string;
48
+ declare function readManifest(manifestPath: string): Promise<SelectorModuleManifest>;
49
+ declare function writeManifest(manifestPath: string, manifest: SelectorModuleManifest): Promise<void>;
50
+ declare function removeStaleSelectorModules(previous: SelectorModuleManifest, next: SelectorModuleManifest): Promise<number>;
56
51
  declare function relativeToRoot(filePath: string, rootDir: string): string;
52
+ declare function ensureSelectorModule(resolvedPath: string, selectors: Map<string, string>, previousManifest: SelectorModuleManifest, nextManifest: SelectorModuleManifest): Promise<boolean>;
57
53
  declare function resolveWithTsconfigPaths(specifier: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
58
54
  declare function loadTsconfigResolutionContext(rootDir: string, loader?: typeof getTsconfig): TsconfigResolutionContext | undefined;
59
55
  declare function normalizeTsconfigPaths(paths: Record<string, string[] | string> | undefined): Record<string, string[]> | undefined;
@@ -65,7 +61,6 @@ export interface ParsedCliArgs {
65
61
  rootDir: string;
66
62
  include?: string[];
67
63
  outDir?: string;
68
- typesRoot?: string;
69
64
  stableNamespace?: string;
70
65
  help?: boolean;
71
66
  }
@@ -76,15 +71,12 @@ declare function setCssWithMetaImplementation(impl?: CssWithMetaFn): void;
76
71
  declare function setModuleTypeDetector(detector?: ModuleTypeDetector): void;
77
72
  declare function setImportMetaUrlProvider(provider?: () => string | undefined): void;
78
73
  export declare const __generateTypesInternals: {
79
- writeTypesIndex: typeof writeTypesIndex;
80
74
  stripInlineLoader: typeof stripInlineLoader;
81
75
  splitResourceAndQuery: typeof splitResourceAndQuery;
76
+ extractSelectorSourceSpecifier: typeof extractSelectorSourceSpecifier;
82
77
  findSpecifierImports: typeof findSpecifierImports;
83
78
  resolveImportPath: typeof resolveImportPath;
84
79
  resolvePackageRoot: typeof resolvePackageRoot;
85
- buildDeclarationFileName: typeof buildDeclarationFileName;
86
- formatModuleDeclaration: typeof formatModuleDeclaration;
87
- formatSelectorType: typeof formatSelectorType;
88
80
  relativeToRoot: typeof relativeToRoot;
89
81
  collectCandidateFiles: typeof collectCandidateFiles;
90
82
  normalizeIncludeOptions: typeof normalizeIncludeOptions;
@@ -100,5 +92,12 @@ export declare const __generateTypesInternals: {
100
92
  parseCliArgs: typeof parseCliArgs;
101
93
  printHelp: typeof printHelp;
102
94
  reportCliResult: typeof reportCliResult;
95
+ buildSelectorModuleManifestKey: typeof buildSelectorModuleManifestKey;
96
+ buildSelectorModulePath: typeof buildSelectorModulePath;
97
+ formatSelectorModuleSource: typeof formatSelectorModuleSource;
98
+ ensureSelectorModule: typeof ensureSelectorModule;
99
+ removeStaleSelectorModules: typeof removeStaleSelectorModules;
100
+ readManifest: typeof readManifest;
101
+ writeManifest: typeof writeManifest;
103
102
  };
104
103
  export {};
@@ -3,38 +3,30 @@ import { moduleType } from 'node-module-type';
3
3
  import { getTsconfig } from 'get-tsconfig';
4
4
  import { type MatchPath } from 'tsconfig-paths';
5
5
  import { cssWithMeta } from './css.js';
6
- import { type SelectorTypeVariant } from './loaderInternals.js';
7
- interface ManifestEntry {
8
- file: string;
9
- hash: string;
10
- }
11
- type Manifest = Record<string, ManifestEntry>;
12
6
  interface ImportMatch {
13
7
  specifier: string;
14
8
  importer: string;
15
9
  }
16
- interface DeclarationRecord {
17
- specifier: string;
18
- filePath: string;
10
+ interface ManifestEntry {
11
+ file: string;
12
+ hash: string;
19
13
  }
14
+ type SelectorModuleManifest = Record<string, ManifestEntry>;
20
15
  interface TsconfigResolutionContext {
21
16
  absoluteBaseUrl?: string;
22
17
  matchPath?: MatchPath;
23
18
  }
24
19
  type CssWithMetaFn = typeof cssWithMeta;
25
20
  export interface GenerateTypesResult {
26
- written: number;
27
- removed: number;
28
- declarations: DeclarationRecord[];
21
+ selectorModulesWritten: number;
22
+ selectorModulesRemoved: number;
29
23
  warnings: string[];
30
- outDir: string;
31
- typesIndexPath: string;
24
+ manifestPath: string;
32
25
  }
33
26
  export interface GenerateTypesOptions {
34
27
  rootDir?: string;
35
28
  include?: string[];
36
29
  outDir?: string;
37
- typesRoot?: string;
38
30
  stableNamespace?: string;
39
31
  }
40
32
  type ModuleTypeDetector = () => ReturnType<typeof moduleType>;
@@ -48,12 +40,16 @@ declare function splitResourceAndQuery(specifier: string): {
48
40
  resource: string;
49
41
  query: string;
50
42
  };
43
+ declare function extractSelectorSourceSpecifier(specifier: string): string | undefined;
51
44
  declare function resolveImportPath(resourceSpecifier: string, importerPath: string, rootDir: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
52
- declare function buildDeclarationFileName(specifier: string): string;
53
- declare function formatModuleDeclaration(specifier: string, variant: SelectorTypeVariant, selectors: Map<string, string>): string;
54
- declare function formatSelectorType(selectors: Map<string, string>): string;
55
- declare function writeTypesIndex(indexPath: string, manifest: Manifest, outDir: string): Promise<void>;
45
+ declare function buildSelectorModuleManifestKey(resolvedPath: string): string;
46
+ declare function buildSelectorModulePath(resolvedPath: string): string;
47
+ declare function formatSelectorModuleSource(selectors: Map<string, string>): string;
48
+ declare function readManifest(manifestPath: string): Promise<SelectorModuleManifest>;
49
+ declare function writeManifest(manifestPath: string, manifest: SelectorModuleManifest): Promise<void>;
50
+ declare function removeStaleSelectorModules(previous: SelectorModuleManifest, next: SelectorModuleManifest): Promise<number>;
56
51
  declare function relativeToRoot(filePath: string, rootDir: string): string;
52
+ declare function ensureSelectorModule(resolvedPath: string, selectors: Map<string, string>, previousManifest: SelectorModuleManifest, nextManifest: SelectorModuleManifest): Promise<boolean>;
57
53
  declare function resolveWithTsconfigPaths(specifier: string, tsconfig?: TsconfigResolutionContext): Promise<string | undefined>;
58
54
  declare function loadTsconfigResolutionContext(rootDir: string, loader?: typeof getTsconfig): TsconfigResolutionContext | undefined;
59
55
  declare function normalizeTsconfigPaths(paths: Record<string, string[] | string> | undefined): Record<string, string[]> | undefined;
@@ -65,7 +61,6 @@ export interface ParsedCliArgs {
65
61
  rootDir: string;
66
62
  include?: string[];
67
63
  outDir?: string;
68
- typesRoot?: string;
69
64
  stableNamespace?: string;
70
65
  help?: boolean;
71
66
  }
@@ -76,15 +71,12 @@ declare function setCssWithMetaImplementation(impl?: CssWithMetaFn): void;
76
71
  declare function setModuleTypeDetector(detector?: ModuleTypeDetector): void;
77
72
  declare function setImportMetaUrlProvider(provider?: () => string | undefined): void;
78
73
  export declare const __generateTypesInternals: {
79
- writeTypesIndex: typeof writeTypesIndex;
80
74
  stripInlineLoader: typeof stripInlineLoader;
81
75
  splitResourceAndQuery: typeof splitResourceAndQuery;
76
+ extractSelectorSourceSpecifier: typeof extractSelectorSourceSpecifier;
82
77
  findSpecifierImports: typeof findSpecifierImports;
83
78
  resolveImportPath: typeof resolveImportPath;
84
79
  resolvePackageRoot: typeof resolvePackageRoot;
85
- buildDeclarationFileName: typeof buildDeclarationFileName;
86
- formatModuleDeclaration: typeof formatModuleDeclaration;
87
- formatSelectorType: typeof formatSelectorType;
88
80
  relativeToRoot: typeof relativeToRoot;
89
81
  collectCandidateFiles: typeof collectCandidateFiles;
90
82
  normalizeIncludeOptions: typeof normalizeIncludeOptions;
@@ -100,5 +92,12 @@ export declare const __generateTypesInternals: {
100
92
  parseCliArgs: typeof parseCliArgs;
101
93
  printHelp: typeof printHelp;
102
94
  reportCliResult: typeof reportCliResult;
95
+ buildSelectorModuleManifestKey: typeof buildSelectorModuleManifestKey;
96
+ buildSelectorModulePath: typeof buildSelectorModulePath;
97
+ formatSelectorModuleSource: typeof formatSelectorModuleSource;
98
+ ensureSelectorModule: typeof ensureSelectorModule;
99
+ removeStaleSelectorModules: typeof removeStaleSelectorModules;
100
+ readManifest: typeof readManifest;
101
+ writeManifest: typeof writeManifest;
103
102
  };
104
103
  export {};
@@ -8,7 +8,6 @@ import { moduleType } from 'node-module-type';
8
8
  import { getTsconfig } from 'get-tsconfig';
9
9
  import { createMatchPath } from 'tsconfig-paths';
10
10
  import { cssWithMeta } from './css.js';
11
- import { determineSelectorVariant, hasQueryFlag, TYPES_QUERY_FLAG, } from './loaderInternals.js';
12
11
  import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js';
13
12
  import { resolveStableNamespace } from './stableNamespace.js';
14
13
  let activeCssWithMeta = cssWithMeta;
@@ -57,22 +56,19 @@ function getImportMetaUrl() {
57
56
  }
58
57
  }
59
58
  const PACKAGE_ROOT = resolvePackageRoot();
60
- const DEFAULT_TYPES_ROOT = path.join(PACKAGE_ROOT, 'types-stub');
61
- const DEFAULT_OUT_DIR = path.join(PACKAGE_ROOT, 'node_modules', '.knighted-css');
59
+ const SELECTOR_REFERENCE = '.knighted-css';
60
+ const SELECTOR_MODULE_SUFFIX = '.knighted-css.ts';
62
61
  export async function generateTypes(options = {}) {
63
62
  const rootDir = path.resolve(options.rootDir ?? process.cwd());
64
63
  const include = normalizeIncludeOptions(options.include, rootDir);
65
- const outDir = path.resolve(options.outDir ?? DEFAULT_OUT_DIR);
66
- const typesRoot = path.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT);
64
+ const cacheDir = path.resolve(options.outDir ?? path.join(rootDir, '.knighted-css'));
67
65
  const tsconfig = loadTsconfigResolutionContext(rootDir);
68
66
  await init;
69
- await fs.mkdir(outDir, { recursive: true });
70
- await fs.mkdir(typesRoot, { recursive: true });
67
+ await fs.mkdir(cacheDir, { recursive: true });
71
68
  const internalOptions = {
72
69
  rootDir,
73
70
  include,
74
- outDir,
75
- typesRoot,
71
+ cacheDir,
76
72
  stableNamespace: options.stableNamespace,
77
73
  tsconfig,
78
74
  };
@@ -81,32 +77,27 @@ export async function generateTypes(options = {}) {
81
77
  async function generateDeclarations(options) {
82
78
  const peerResolver = createProjectPeerResolver(options.rootDir);
83
79
  const files = await collectCandidateFiles(options.include);
84
- const manifestPath = path.join(options.outDir, 'manifest.json');
85
- const previousManifest = await readManifest(manifestPath);
86
- const nextManifest = {};
80
+ const selectorModulesManifestPath = path.join(options.cacheDir, 'selector-modules.json');
81
+ const previousSelectorManifest = await readManifest(selectorModulesManifestPath);
82
+ const nextSelectorManifest = {};
87
83
  const selectorCache = new Map();
88
- const processedSpecifiers = new Set();
89
- const declarations = [];
84
+ const processedSelectors = new Set();
90
85
  const warnings = [];
91
- let writes = 0;
86
+ let selectorModuleWrites = 0;
92
87
  for (const filePath of files) {
93
88
  const matches = await findSpecifierImports(filePath);
94
89
  for (const match of matches) {
95
90
  const cleaned = match.specifier.trim();
96
91
  const inlineFree = stripInlineLoader(cleaned);
97
- if (!inlineFree.includes('?knighted-css'))
98
- continue;
99
- const { resource, query } = splitResourceAndQuery(inlineFree);
100
- if (!query || !hasQueryFlag(query, TYPES_QUERY_FLAG)) {
101
- continue;
102
- }
103
- if (processedSpecifiers.has(cleaned)) {
92
+ const { resource } = splitResourceAndQuery(inlineFree);
93
+ const selectorSource = extractSelectorSourceSpecifier(resource);
94
+ if (!selectorSource) {
104
95
  continue;
105
96
  }
106
97
  const resolvedNamespace = resolveStableNamespace(options.stableNamespace);
107
- const resolvedPath = await resolveImportPath(resource, match.importer, options.rootDir, options.tsconfig);
98
+ const resolvedPath = await resolveImportPath(selectorSource, match.importer, options.rootDir, options.tsconfig);
108
99
  if (!resolvedPath) {
109
- warnings.push(`Unable to resolve ${resource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
100
+ warnings.push(`Unable to resolve ${selectorSource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`);
110
101
  continue;
111
102
  }
112
103
  const cacheKey = `${resolvedPath}::${resolvedNamespace}`;
@@ -130,38 +121,28 @@ async function generateDeclarations(options) {
130
121
  }
131
122
  selectorCache.set(cacheKey, selectorMap);
132
123
  }
133
- const variant = determineSelectorVariant(query);
134
- const declaration = formatModuleDeclaration(cleaned, variant, selectorMap);
135
- const declarationHash = hashContent(declaration);
136
- const fileName = buildDeclarationFileName(cleaned);
137
- const targetPath = path.join(options.outDir, fileName);
138
- const previousEntry = previousManifest[cleaned];
139
- const needsWrite = previousEntry?.hash !== declarationHash || !(await fileExists(targetPath));
140
- if (needsWrite) {
141
- await fs.writeFile(targetPath, declaration, 'utf8');
142
- writes += 1;
124
+ if (!isWithinRoot(resolvedPath, options.rootDir)) {
125
+ warnings.push(`Skipping selector module for ${relativeToRoot(resolvedPath, options.rootDir)} because it is outside the project root.`);
126
+ continue;
127
+ }
128
+ const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
129
+ if (processedSelectors.has(manifestKey)) {
130
+ continue;
143
131
  }
144
- nextManifest[cleaned] = { file: fileName, hash: declarationHash };
145
- if (needsWrite) {
146
- declarations.push({ specifier: cleaned, filePath: targetPath });
132
+ const moduleWrite = await ensureSelectorModule(resolvedPath, selectorMap, previousSelectorManifest, nextSelectorManifest);
133
+ if (moduleWrite) {
134
+ selectorModuleWrites += 1;
147
135
  }
148
- processedSpecifiers.add(cleaned);
136
+ processedSelectors.add(manifestKey);
149
137
  }
150
138
  }
151
- const removed = await removeStaleDeclarations(previousManifest, nextManifest, options.outDir);
152
- await writeManifest(manifestPath, nextManifest);
153
- const typesIndexPath = path.join(options.typesRoot, 'index.d.ts');
154
- await writeTypesIndex(typesIndexPath, nextManifest, options.outDir);
155
- if (Object.keys(nextManifest).length === 0) {
156
- declarations.length = 0;
157
- }
139
+ const selectorModulesRemoved = await removeStaleSelectorModules(previousSelectorManifest, nextSelectorManifest);
140
+ await writeManifest(selectorModulesManifestPath, nextSelectorManifest);
158
141
  return {
159
- written: writes,
160
- removed,
161
- declarations,
142
+ selectorModulesWritten: selectorModuleWrites,
143
+ selectorModulesRemoved,
162
144
  warnings,
163
- outDir: options.outDir,
164
- typesIndexPath,
145
+ manifestPath: selectorModulesManifestPath,
165
146
  };
166
147
  }
167
148
  function normalizeIncludeOptions(include, rootDir) {
@@ -219,18 +200,18 @@ async function findSpecifierImports(filePath) {
219
200
  catch {
220
201
  return [];
221
202
  }
222
- if (!source.includes('?knighted-css')) {
203
+ if (!source.includes(SELECTOR_REFERENCE)) {
223
204
  return [];
224
205
  }
225
206
  const matches = [];
226
207
  const [imports] = parse(source, filePath);
227
208
  for (const record of imports) {
228
209
  const specifier = record.n ?? source.slice(record.s, record.e);
229
- if (specifier && specifier.includes('?knighted-css')) {
210
+ if (specifier && specifier.includes(SELECTOR_REFERENCE)) {
230
211
  matches.push({ specifier, importer: filePath });
231
212
  }
232
213
  }
233
- const requireRegex = /require\((['"])([^'"`]+?\?knighted-css[^'"`]*)\1\)/g;
214
+ const requireRegex = /require\((['"])([^'"`]+?\.knighted-css[^'"`]*)\1\)/g;
234
215
  let reqMatch;
235
216
  while ((reqMatch = requireRegex.exec(source)) !== null) {
236
217
  const spec = reqMatch[2];
@@ -253,6 +234,21 @@ function splitResourceAndQuery(specifier) {
253
234
  }
254
235
  return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) };
255
236
  }
237
+ function extractSelectorSourceSpecifier(specifier) {
238
+ const markerIndex = specifier.indexOf(SELECTOR_REFERENCE);
239
+ if (markerIndex < 0) {
240
+ return undefined;
241
+ }
242
+ const suffix = specifier.slice(markerIndex + SELECTOR_REFERENCE.length);
243
+ if (suffix.length > 0 && !/\.(?:[cm]?[tj]s|[tj]sx)$/.test(suffix)) {
244
+ return undefined;
245
+ }
246
+ const base = specifier.slice(0, markerIndex);
247
+ if (!base) {
248
+ return undefined;
249
+ }
250
+ return base;
251
+ }
256
252
  const projectRequireCache = new Map();
257
253
  async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tsconfig) {
258
254
  if (!resourceSpecifier)
@@ -275,47 +271,29 @@ async function resolveImportPath(resourceSpecifier, importerPath, rootDir, tscon
275
271
  return undefined;
276
272
  }
277
273
  }
278
- function buildDeclarationFileName(specifier) {
279
- const digest = crypto.createHash('sha1').update(specifier).digest('hex').slice(0, 12);
280
- return `knt-${digest}.d.ts`;
281
- }
282
- function formatModuleDeclaration(specifier, variant, selectors) {
283
- const literalSpecifier = JSON.stringify(specifier);
284
- const selectorType = formatSelectorType(selectors);
285
- const header = `declare module ${literalSpecifier} {`;
286
- const footer = '}';
287
- if (variant === 'types') {
288
- return `${header}
289
- export const knightedCss: string
290
- export const stableSelectors: ${selectorType}
291
- ${footer}
292
- `;
293
- }
294
- const stableLine = ` export const stableSelectors: ${selectorType}`;
295
- const shared = ` const combined: KnightedCssCombinedModule<Record<string, unknown>>
296
- export const knightedCss: string
297
- ${stableLine}`;
298
- if (variant === 'combined') {
299
- return `${header}
300
- ${shared}
301
- export default combined
302
- ${footer}
303
- `;
304
- }
305
- return `${header}
306
- ${shared}
307
- ${footer}
308
- `;
274
+ function buildSelectorModuleManifestKey(resolvedPath) {
275
+ return resolvedPath.split(path.sep).join('/');
309
276
  }
310
- function formatSelectorType(selectors) {
311
- if (selectors.size === 0) {
312
- return 'Readonly<Record<string, string>>';
313
- }
277
+ function buildSelectorModulePath(resolvedPath) {
278
+ return `${resolvedPath}${SELECTOR_MODULE_SUFFIX}`;
279
+ }
280
+ function formatSelectorModuleSource(selectors) {
281
+ const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
314
282
  const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b));
315
- const lines = entries.map(([token, selector]) => ` readonly ${JSON.stringify(token)}: ${JSON.stringify(selector)}`);
316
- return `Readonly<{
283
+ const lines = entries.map(([token, selector]) => ` ${JSON.stringify(token)}: ${JSON.stringify(selector)},`);
284
+ const literal = lines.length > 0
285
+ ? `{
317
286
  ${lines.join('\n')}
318
- }>`;
287
+ } as const`
288
+ : '{} as const';
289
+ return `${header}
290
+ export const stableSelectors = ${literal}
291
+
292
+ export type KnightedCssStableSelectors = typeof stableSelectors
293
+ export type KnightedCssStableSelectorToken = keyof typeof stableSelectors
294
+
295
+ export default stableSelectors
296
+ `;
319
297
  }
320
298
  function hashContent(content) {
321
299
  return crypto.createHash('sha1').update(content).digest('hex');
@@ -332,13 +310,12 @@ async function readManifest(manifestPath) {
332
310
  async function writeManifest(manifestPath, manifest) {
333
311
  await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
334
312
  }
335
- async function removeStaleDeclarations(previous, next, outDir) {
336
- const stale = Object.entries(previous).filter(([specifier]) => !next[specifier]);
313
+ async function removeStaleSelectorModules(previous, next) {
314
+ const stale = Object.entries(previous).filter(([key]) => !next[key]);
337
315
  let removed = 0;
338
316
  for (const [, entry] of stale) {
339
- const targetPath = path.join(outDir, entry.file);
340
317
  try {
341
- await fs.unlink(targetPath);
318
+ await fs.unlink(entry.file);
342
319
  removed += 1;
343
320
  }
344
321
  catch {
@@ -347,25 +324,6 @@ async function removeStaleDeclarations(previous, next, outDir) {
347
324
  }
348
325
  return removed;
349
326
  }
350
- async function writeTypesIndex(indexPath, manifest, outDir) {
351
- const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n';
352
- const references = Object.values(manifest)
353
- .sort((a, b) => a.file.localeCompare(b.file))
354
- .map(entry => {
355
- const rel = path
356
- .relative(path.dirname(indexPath), path.join(outDir, entry.file))
357
- .split(path.sep)
358
- .join('/');
359
- return `/// <reference path="${rel}" />`;
360
- });
361
- const content = references.length > 0
362
- ? `${header}
363
- ${references.join('\n')}
364
- `
365
- : `${header}
366
- `;
367
- await fs.writeFile(indexPath, content, 'utf8');
368
- }
369
327
  function formatErrorMessage(error) {
370
328
  if (error instanceof Error && typeof error.message === 'string') {
371
329
  return error.message;
@@ -375,6 +333,23 @@ function formatErrorMessage(error) {
375
333
  function relativeToRoot(filePath, rootDir) {
376
334
  return path.relative(rootDir, filePath) || filePath;
377
335
  }
336
+ function isWithinRoot(filePath, rootDir) {
337
+ const relative = path.relative(rootDir, filePath);
338
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
339
+ }
340
+ async function ensureSelectorModule(resolvedPath, selectors, previousManifest, nextManifest) {
341
+ const manifestKey = buildSelectorModuleManifestKey(resolvedPath);
342
+ const targetPath = buildSelectorModulePath(resolvedPath);
343
+ const source = formatSelectorModuleSource(selectors);
344
+ const hash = hashContent(source);
345
+ const previousEntry = previousManifest[manifestKey];
346
+ const needsWrite = previousEntry?.hash !== hash || !(await fileExists(targetPath));
347
+ if (needsWrite) {
348
+ await fs.writeFile(targetPath, source, 'utf8');
349
+ }
350
+ nextManifest[manifestKey] = { file: targetPath, hash };
351
+ return needsWrite;
352
+ }
378
353
  async function fileExists(target) {
379
354
  try {
380
355
  await fs.access(target);
@@ -498,7 +473,6 @@ export async function runGenerateTypesCli(argv = process.argv.slice(2)) {
498
473
  rootDir: parsed.rootDir,
499
474
  include: parsed.include,
500
475
  outDir: parsed.outDir,
501
- typesRoot: parsed.typesRoot,
502
476
  stableNamespace: parsed.stableNamespace,
503
477
  });
504
478
  reportCliResult(result);
@@ -513,12 +487,11 @@ function parseCliArgs(argv) {
513
487
  let rootDir = process.cwd();
514
488
  const include = [];
515
489
  let outDir;
516
- let typesRoot;
517
490
  let stableNamespace;
518
491
  for (let i = 0; i < argv.length; i += 1) {
519
492
  const arg = argv[i];
520
493
  if (arg === '--help' || arg === '-h') {
521
- return { rootDir, include, outDir, typesRoot, stableNamespace, help: true };
494
+ return { rootDir, include, outDir, stableNamespace, help: true };
522
495
  }
523
496
  if (arg === '--root' || arg === '-r') {
524
497
  const value = argv[++i];
@@ -544,14 +517,6 @@ function parseCliArgs(argv) {
544
517
  outDir = value;
545
518
  continue;
546
519
  }
547
- if (arg === '--types-root') {
548
- const value = argv[++i];
549
- if (!value) {
550
- throw new Error('Missing value for --types-root');
551
- }
552
- typesRoot = value;
553
- continue;
554
- }
555
520
  if (arg === '--stable-namespace') {
556
521
  const value = argv[++i];
557
522
  if (!value) {
@@ -565,7 +530,7 @@ function parseCliArgs(argv) {
565
530
  }
566
531
  include.push(arg);
567
532
  }
568
- return { rootDir, include, outDir, typesRoot, stableNamespace };
533
+ return { rootDir, include, outDir, stableNamespace };
569
534
  }
570
535
  function printHelp() {
571
536
  console.log(`Usage: knighted-css-generate-types [options]
@@ -573,20 +538,19 @@ function printHelp() {
573
538
  Options:
574
539
  -r, --root <path> Project root directory (default: cwd)
575
540
  -i, --include <path> Additional directories/files to scan (repeatable)
576
- --out-dir <path> Output directory for generated declarations
577
- --types-root <path> Directory for generated @types entrypoint
541
+ --out-dir <path> Directory to store selector module manifest cache
578
542
  --stable-namespace <name> Stable namespace prefix for generated selector maps
579
543
  -h, --help Show this help message
580
544
  `);
581
545
  }
582
546
  function reportCliResult(result) {
583
- if (result.written === 0 && result.removed === 0) {
584
- console.log('[knighted-css] No changes to ?knighted-css&types declarations (cache is up to date).');
547
+ if (result.selectorModulesWritten === 0 && result.selectorModulesRemoved === 0) {
548
+ console.log('[knighted-css] Selector modules are up to date.');
585
549
  }
586
550
  else {
587
- console.log(`[knighted-css] Updated ${result.written} declaration(s), removed ${result.removed}, output in ${result.outDir}.`);
551
+ console.log(`[knighted-css] Selector modules updated: wrote ${result.selectorModulesWritten}, removed ${result.selectorModulesRemoved}.`);
588
552
  }
589
- console.log(`[knighted-css] Type references: ${result.typesIndexPath}`);
553
+ console.log(`[knighted-css] Manifest: ${result.manifestPath}`);
590
554
  for (const warning of result.warnings) {
591
555
  console.warn(`[knighted-css] ${warning}`);
592
556
  }
@@ -601,15 +565,12 @@ function setImportMetaUrlProvider(provider) {
601
565
  importMetaUrlProvider = provider ?? getImportMetaUrl;
602
566
  }
603
567
  export const __generateTypesInternals = {
604
- writeTypesIndex,
605
568
  stripInlineLoader,
606
569
  splitResourceAndQuery,
570
+ extractSelectorSourceSpecifier,
607
571
  findSpecifierImports,
608
572
  resolveImportPath,
609
573
  resolvePackageRoot,
610
- buildDeclarationFileName,
611
- formatModuleDeclaration,
612
- formatSelectorType,
613
574
  relativeToRoot,
614
575
  collectCandidateFiles,
615
576
  normalizeIncludeOptions,
@@ -625,4 +586,11 @@ export const __generateTypesInternals = {
625
586
  parseCliArgs,
626
587
  printHelp,
627
588
  reportCliResult,
589
+ buildSelectorModuleManifestKey,
590
+ buildSelectorModulePath,
591
+ formatSelectorModuleSource,
592
+ ensureSelectorModule,
593
+ removeStaleSelectorModules,
594
+ readManifest,
595
+ writeManifest,
628
596
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knighted/css",
3
- "version": "1.0.0-rc.10",
3
+ "version": "1.0.0-rc.12",
4
4
  "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.",
5
5
  "type": "module",
6
6
  "main": "./dist/css.js",