@actioneer/ads 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/ads-init.mjs CHANGED
@@ -254,6 +254,15 @@ const CANDIDATE_CSS = [
254
254
  ];
255
255
  const CANDIDATE_LAYOUT = ["app/layout.tsx", "src/app/layout.tsx", "app/layout.jsx", "src/app/layout.jsx"];
256
256
 
257
+ // The load-bearing anchor: ADS styles must import into the SAME CSS graph as
258
+ // tailwindcss, right after it (styles.css before tailwindcss leaves @theme
259
+ // inert). Detection keys off this so a non-standard stylesheet is still found.
260
+ const TAILWIND_IMPORT_RE = /@import\s+["']tailwindcss["']/;
261
+ // Dirs the stylesheet scan never descends into.
262
+ const SCAN_IGNORE = new Set([
263
+ "node_modules", ".git", ".next", "dist", "dist-lib", "build", "out", "coverage", ".turbo", ".vercel", ".cache",
264
+ ]);
265
+
257
266
  function toPosix(p) {
258
267
  return p.split(path.sep).join("/");
259
268
  }
@@ -261,6 +270,81 @@ function fileExistsInState(state, relPosix) {
261
270
  return state._existing?.has(relPosix) ?? false;
262
271
  }
263
272
 
273
+ /**
274
+ * Find the global stylesheet to wire ADS styles into. Because the import order
275
+ * is load-bearing, detection PREFERS the stylesheet that already imports
276
+ * tailwindcss — first among the conventional locations, then ANYWHERE in the
277
+ * project (bounded scan) so a non-standard path (e.g. `src/index.css`,
278
+ * `app/tailwind.css`) is auto-wired instead of degrading to a hand-edit note.
279
+ * Falls back to the first conventional file that exists (→ manual, with anchor
280
+ * guidance), else null.
281
+ * @returns {{path: string|null, content: string|null}}
282
+ */
283
+ export function detectGlobalCss(cwd) {
284
+ const read = (rel) => {
285
+ const abs = path.join(cwd, rel);
286
+ return fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : null;
287
+ };
288
+ // 1. Conventional location that already imports tailwindcss (the anchor).
289
+ for (const p of CANDIDATE_CSS) {
290
+ const c = read(p);
291
+ if (c != null && TAILWIND_IMPORT_RE.test(c)) return { path: p, content: c };
292
+ }
293
+ // 2. Any .css in the project importing tailwindcss (covers non-standard paths).
294
+ const scanned = scanForTailwindCss(cwd);
295
+ if (scanned) return scanned;
296
+ // 3. A conventional file exists but has no tailwindcss anchor → manual path.
297
+ for (const p of CANDIDATE_CSS) {
298
+ const c = read(p);
299
+ if (c != null) return { path: p, content: c };
300
+ }
301
+ return { path: null, content: null };
302
+ }
303
+
304
+ /** Bounded walk for the shallowest .css that imports tailwindcss. */
305
+ function scanForTailwindCss(cwd, maxDepth = 5) {
306
+ const hits = [];
307
+ const walk = (rel, depth) => {
308
+ if (depth > maxDepth) return;
309
+ let entries;
310
+ try {
311
+ entries = fs.readdirSync(path.join(cwd, rel || "."), { withFileTypes: true });
312
+ } catch {
313
+ return;
314
+ }
315
+ for (const e of entries) {
316
+ if (e.name.startsWith(".")) continue;
317
+ const childRel = rel ? `${rel}/${e.name}` : e.name;
318
+ if (e.isDirectory()) {
319
+ if (!SCAN_IGNORE.has(e.name)) walk(childRel, depth + 1);
320
+ } else if (e.name.endsWith(".css")) {
321
+ let c;
322
+ try {
323
+ c = fs.readFileSync(path.join(cwd, childRel), "utf8");
324
+ } catch {
325
+ continue;
326
+ }
327
+ if (TAILWIND_IMPORT_RE.test(c)) hits.push({ path: toPosix(childRel), content: c });
328
+ }
329
+ }
330
+ };
331
+ walk("", 0);
332
+ // Shallowest path wins (the app entry over a nested one); stable tiebreak.
333
+ hits.sort((a, b) => a.path.split("/").length - b.path.split("/").length || a.path.localeCompare(b.path));
334
+ return hits[0] ?? null;
335
+ }
336
+
337
+ /**
338
+ * Re-check the load-bearing outcome after init: is `@import "…/styles.css"`
339
+ * actually present in the detected global stylesheet? Drives the end-of-run
340
+ * summary so a missed/deferred styles import can't scroll past unnoticed.
341
+ * @returns {{ok: boolean, path: string|null}}
342
+ */
343
+ export function verifyStylesWired(cwd) {
344
+ const css = detectGlobalCss(cwd);
345
+ return { ok: css.content != null && css.content.includes(STYLE_IMPORT), path: css.path };
346
+ }
347
+
264
348
  function readRepoState(cwd) {
265
349
  const read = (rel) => {
266
350
  const abs = path.join(cwd, rel);
@@ -276,7 +360,7 @@ function readRepoState(cwd) {
276
360
  lockfiles: LOCKFILES.filter((f) => fs.existsSync(path.join(cwd, f))),
277
361
  });
278
362
 
279
- const globalCssPath = CANDIDATE_CSS.find((p) => fs.existsSync(path.join(cwd, p))) ?? null;
363
+ const css = detectGlobalCss(cwd);
280
364
  const layoutPath = CANDIDATE_LAYOUT.find((p) => fs.existsSync(path.join(cwd, p))) ?? null;
281
365
 
282
366
  const _existing = new Set();
@@ -286,8 +370,8 @@ function readRepoState(cwd) {
286
370
 
287
371
  return {
288
372
  dependencies,
289
- globalCssPath,
290
- globalCssContent: globalCssPath ? read(globalCssPath) : null,
373
+ globalCssPath: css.path,
374
+ globalCssContent: css.content,
291
375
  layoutPath,
292
376
  layoutContent: layoutPath ? read(layoutPath) : null,
293
377
  hasAdsLintConfig:
@@ -336,6 +420,47 @@ function applyEdit(cwd, edit) {
336
420
  }
337
421
  }
338
422
 
423
+ /**
424
+ * End-of-run summary. The styles import carries EVERY token (colors, type, and
425
+ * the animation curves/speeds), so its outcome gets promoted out of the
426
+ * scrolling edit log into a verified ✓ / ⚠ block that can't be missed — plus any
427
+ * remaining manual steps and the import hint.
428
+ */
429
+ function printSummary(cwd, edits) {
430
+ const wired = verifyStylesWired(cwd);
431
+ const bar = "─".repeat(70);
432
+
433
+ console.log("");
434
+ if (wired.ok) {
435
+ console.log(`✓ ADS styles wired — \`${STYLE_IMPORT}\` is in ${wired.path}.`);
436
+ console.log(` That one import carries every ADS token: colors, typography, and the`);
437
+ console.log(` animation curves/speeds.`);
438
+ } else {
439
+ console.log(bar);
440
+ console.log(`⚠ ACTION REQUIRED — ADS styles are NOT imported yet.`);
441
+ console.log(``);
442
+ console.log(` Nothing will be themed until you add this to your global stylesheet,`);
443
+ console.log(` immediately AFTER the tailwindcss import (the order is load-bearing):`);
444
+ console.log(``);
445
+ console.log(` @import "tailwindcss";`);
446
+ console.log(` ${STYLE_IMPORT}`);
447
+ console.log(``);
448
+ console.log(` Without it NO tokens load — colors, typography, and every animation`);
449
+ console.log(` curve/speed will be missing.`);
450
+ console.log(bar);
451
+ }
452
+
453
+ // Surface any non-styles manual step (e.g. the layout <Providers> wrap) too —
454
+ // the styles step is already covered by the block above.
455
+ const otherManual = edits.filter((e) => e.type === "manual" && !/styles\.css/.test(e.title));
456
+ if (otherManual.length) {
457
+ console.log(`\n Remaining manual step(s):`);
458
+ for (const m of otherManual) console.log(` • ${m.title}`);
459
+ }
460
+
461
+ console.log(`\n Then import components: \`import { Button } from "${PKG}";\``);
462
+ }
463
+
339
464
  function main() {
340
465
  const cmd = process.argv[2];
341
466
  const cwd = process.cwd();
@@ -349,12 +474,15 @@ function main() {
349
474
 
350
475
  if (edits.length === 0) {
351
476
  console.log(`${PKG} init: nothing to do — already configured.`);
352
- return;
477
+ } else {
478
+ console.log(`${PKG} init: applying ${edits.length} edit(s)…`);
479
+ for (const edit of edits) applyEdit(cwd, edit);
480
+ console.log(`\n${PKG} init: done.`);
353
481
  }
354
482
 
355
- console.log(`${PKG} init: applying ${edits.length} edit(s)…`);
356
- for (const edit of edits) applyEdit(cwd, edit);
357
- console.log(`\n${PKG} init: done. Import components with \`import { Button } from "${PKG}";\``);
483
+ // Always verify + summarize the load-bearing styles import (fixes #2: a
484
+ // deferred/missed styles import can no longer scroll past unnoticed).
485
+ printSummary(cwd, edits);
358
486
  }
359
487
 
360
488
  /**
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actioneer/ads",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "importPath": "@actioneer/ads",
5
5
  "styleImport": "@import \"@actioneer/ads/styles.css\";",
6
6
  "families": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actioneer/ads",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Actioneer Design System 1.0 — React 19 + Tailwind v4 component library.",
5
5
  "type": "module",
6
6
  "sideEffects": [