create-lupine 1.0.23 → 1.0.24

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/index.js CHANGED
@@ -6,17 +6,23 @@ import { fileURLToPath } from 'node:url';
6
6
  import crypto from 'node:crypto';
7
7
  import { execSync } from 'node:child_process';
8
8
 
9
- function getLatestVersion(pkgName, fallback) {
10
- try {
11
- const version = execSync(`npm view ${pkgName} version`, {
12
- encoding: 'utf8',
13
- stdio: ['ignore', 'pipe', 'ignore'],
14
- timeout: 3000,
15
- }).trim();
16
- return version ? `^${version}` : fallback;
17
- } catch (e) {
18
- return fallback;
9
+ function getLatestVersion(pkgName, fallback, retries = 2) {
10
+ for (let i = 0; i <= retries; i++) {
11
+ try {
12
+ const version = execSync(`npm view ${pkgName} version`, {
13
+ encoding: 'utf8',
14
+ stdio: ['ignore', 'pipe', 'ignore'],
15
+ timeout: 5000,
16
+ }).trim();
17
+ return version ? `^${version}` : fallback;
18
+ } catch (e) {
19
+ if (i === retries) {
20
+ console.warn(`\x1b[33mWarning: Failed to fetch latest version for ${pkgName}. Using fallback ${fallback}\x1b[0m`);
21
+ return fallback;
22
+ }
23
+ }
19
24
  }
25
+ return fallback;
20
26
  }
21
27
 
22
28
  function generateRandomString(length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lupine",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
4
4
  "description": "Scaffolding tool for Lupine.js projects",
5
5
  "bin": {
6
6
  "create-lupine": "index.js"
@@ -76,23 +76,23 @@ Since Lupine.js uses a CSS-in-JS styling approach, when you need to define or ov
76
76
  // 1. Separate theme variables into their own CSS object
77
77
  const cssTheme: CssProps = {
78
78
  '[data-theme="light" i]': {
79
- "--my-comp-bg-color": "#e6e6e6",
79
+ '--my-comp-bg-color': '#e6e6e6',
80
80
  },
81
81
  '[data-theme="dark" i]': {
82
- "--my-comp-bg-color": "var(--primary-accent-color)",
82
+ '--my-comp-bg-color': 'var(--primary-accent-color)',
83
83
  },
84
84
  };
85
85
  // 2. Bind globally. Param 4 (noTopClassName) MUST be true to prevent injecting a namespace prefix.
86
- bindGlobalStyle("my-comp-theme", cssTheme, false, true);
86
+ bindGlobalStyle('my-comp-theme', cssTheme, false, true);
87
87
 
88
88
  // 3. Use the variable in your standard component styles
89
89
  const css: CssProps = {
90
- ".&-element": {
91
- backgroundColor: "var(--my-comp-bg-color)",
90
+ '.&-element': {
91
+ backgroundColor: 'var(--my-comp-bg-color)',
92
92
  },
93
93
  };
94
94
  // Bind your component styles normally
95
- bindGlobalStyle("my-comp-main", css);
95
+ bindGlobalStyle('my-comp-main', css);
96
96
  ```
97
97
 
98
98
  #### 🎨 Color Variable Semantics (CRITICAL FOR DARK MODE)
@@ -131,22 +131,22 @@ export const MyComponent = () => {
131
131
  const ref: RefProps = {
132
132
  onLoad: async () => {
133
133
  // 3. Querying namespaced elements
134
- const btn = ref.$(".&-btn");
135
- btn.innerHTML = "Ready";
134
+ const btn = ref.$('.&-btn');
135
+ btn.innerHTML = 'Ready';
136
136
  },
137
137
  };
138
138
 
139
139
  const css: CssProps = {
140
140
  // Top-level rules apply to the root component container itself
141
- width: "100%",
142
- padding: "1rem",
141
+ width: '100%',
142
+ padding: '1rem',
143
143
 
144
144
  // 1. Defining namespaced sub-classes in CSS:
145
- ".&-title": { fontWeight: "bold" },
146
- ".&-btn": {
145
+ '.&-title': { fontWeight: 'bold' },
146
+ '.&-btn': {
147
147
  // Nesting pseudo-classes and combination modifiers (no space after &)
148
- "&:hover": { background: "#f0f0f0" },
149
- "&.active": { color: "var(--primary-accent-color)" },
148
+ '&:hover': { background: '#f0f0f0' },
149
+ '&.active': { color: 'var(--primary-accent-color)' },
150
150
  },
151
151
  };
152
152
 
@@ -154,8 +154,8 @@ export const MyComponent = () => {
154
154
  // Setting css={css} safely bounds this style scope
155
155
  <aside css={css} ref={ref}>
156
156
  {/* 2. Applying namespaced classes in JSX */}
157
- <div class="&-title">Hello</div>
158
- <button class="&-btn active">Click Me</button>
157
+ <div class='&-title'>Hello</div>
158
+ <button class='&-btn active'>Click Me</button>
159
159
  </aside>
160
160
  );
161
161
  };
@@ -214,24 +214,19 @@ If your component divides its logic so that some internal floating DOM elements
214
214
  To force separated local DOM partitions to share the exact same `&` CSS Scope as their parent page, explicitly share a globally unique CSS ID using `globalStyleUniqueId()`:
215
215
 
216
216
  ```tsx
217
- import {
218
- globalStyleUniqueId,
219
- HtmlVar,
220
- RefProps,
221
- CssProps,
222
- } from "lupine.components";
217
+ import { globalStyleUniqueId, HtmlVar, RefProps, CssProps } from 'lupine.components';
223
218
 
224
219
  export const HomePage = () => {
225
220
  // 1. Generate a manual ID for the container scope beforehand
226
221
  const cssId = globalStyleUniqueId();
227
222
 
228
- const listDom = new HtmlVar("");
223
+ const listDom = new HtmlVar('');
229
224
 
230
225
  const renderList = () => {
231
226
  // 2. Explicitly bind the inner detached DOM to the parent's globalCssId
232
227
  listDom.value = (
233
- <div ref={{ globalCssId: cssId }} class="&-bundle-container">
234
- <div class="&-bundle-name">Basic Bundle</div>
228
+ <div ref={{ globalCssId: cssId }} class='&-bundle-container'>
229
+ <div class='&-bundle-name'>Basic Bundle</div>
235
230
  </div>
236
231
  );
237
232
  };
@@ -240,7 +235,7 @@ export const HomePage = () => {
240
235
  globalCssId: cssId, // 3. The parent registers the ID as well
241
236
  onLoad: async () => renderList(),
242
237
  };
243
- const css: CssProps = { ".&-bundle-name": { color: "red" } };
238
+ const css: CssProps = { '.&-bundle-name': { color: 'red' } };
244
239
 
245
240
  return (
246
241
  <div css={css} ref={ref}>
@@ -253,24 +248,24 @@ export const HomePage = () => {
253
248
 
254
249
  ### Using `&` on Top-Level Tags
255
250
 
256
- The following example illustrates how to correctly use `&` in the `class` of a top-level tag.
251
+ The following example illustrates how to correctly use `&` in the `class` of a top-level tag.
257
252
 
258
253
  Generally, you should not need to use `&` classes on the top-level tag because you can reference the top-level tag directly via `ref.current`. For styling, the first-level styles defined directly under your `CssProps` object are automatically applied to the top-level tag (e.g., `color: 'red'` below).
259
254
 
260
- However, when there is a special need to use an `&-` class prefix on the top-level tag, you must be careful: **`"&.&-box"`** is the correct syntax. This is because the standalone `&` selector is replaced by both the explicit `gCssId` and the CSS ID automatically generated for this top-level tag.
255
+ However, when there is a special need to use an `&-` class prefix on the top-level tag, you must be careful: **`"&.&-box"`** is the correct syntax. This is because the standalone `&` selector is replaced by both the explicit `gCssId` and the CSS ID automatically generated for this top-level tag.
261
256
 
262
257
  For instance, if `gCssId="g00"` and the auto-generated CSS ID applied by the `ref` is `"l01"`, then `"&.&-box"` compiles to `"g00.g00-box, l01.l01-box"`.
263
258
 
264
259
  Similarly, a nested selector like `"&.&-box .&-item"` will be compiled into `"g00.g00-box .g00-item, l01.l01-box .l01-item"`.
265
260
 
266
- *(Alternatively, if you define the class without the `&-` prefix like `class="box"`, you would target it using `"&.box"`).*
261
+ _(Alternatively, if you define the class without the `&-` prefix like `class="box"`, you would target it using `"&.box"`)._
262
+
267
263
  ```typescript
268
264
  export const Component1 = () => {
269
-
270
265
  const css: CssProps = {
271
266
  color: 'red',
272
- "&.&-box": { fontWeight: 'bold' },
273
- "&.&-box .&-item": { backgroundColor: 'blue' },
267
+ '&.&-box': { fontWeight: 'bold' },
268
+ '&.&-box .&-item': { backgroundColor: 'blue' },
274
269
  };
275
270
  const gCssId = getGlobalStylesId(css);
276
271
  bindGlobalStyle(gCssId, css);
@@ -286,8 +281,6 @@ export const Component1 = () => {
286
281
  };
287
282
  ```
288
283
 
289
-
290
-
291
284
  ---
292
285
 
293
286
  ## 5. Common Patterns ("The Lupine Way")
@@ -319,7 +312,7 @@ const MyPage = () => {
319
312
  // 4. Events
320
313
  const onSearch = async () => {
321
314
  // Read directly from DOM
322
- const query = ref.$("input.&-search").value;
315
+ const query = ref.$('input.&-search').value;
323
316
  // Update logic var
324
317
  pageIndex = 0;
325
318
  // Update UI manually
@@ -334,7 +327,7 @@ const MyPage = () => {
334
327
 
335
328
  return (
336
329
  <div ref={ref}>
337
- <input class="&-search" />
330
+ <input class='&-search' />
338
331
  <button onClick={onSearch}>Go</button>
339
332
  {/* Embed Dynamic Content */}
340
333
  {listDom.node}
@@ -352,11 +345,11 @@ When performing imperative or programmatic routing via JavaScript (e.g. clicking
352
345
  Instead, import and use `initializePage`:
353
346
 
354
347
  ```typescript
355
- import { initializePage } from "lupine.web";
348
+ import { initializePage } from 'lupine.web';
356
349
 
357
350
  const navigate = () => {
358
351
  // CORRECT: Seamless SPA transition
359
- initializePage("/play/diff01/1");
352
+ initializePage('/play/diff01/1');
360
353
 
361
354
  // ERROR / ANTI-PATTERN: Forces full browser reload unless explicitly desired
362
355
  // window.location.href = '/play/diff01/1';
@@ -368,11 +361,7 @@ const navigate = () => {
368
361
  Lupine uses a "Slide-over" model for navigation (Drill-down). To achieve infinite nesting (where a child page can open a grandchild page), each Component level simply needs to define its own `sliderHook` and its own `<SliderFrame>` tag to act as the placeholder for its children.
369
362
 
370
363
  ```typescript
371
- import {
372
- SliderFrame,
373
- SliderFrameHookProps,
374
- HeaderWithBackFrame,
375
- } from "lupine.components";
364
+ import { SliderFrame, SliderFrameHookProps, HeaderWithBackFrame } from 'lupine.components';
376
365
 
377
366
  // 1. Parent Component (or Level 1)
378
367
  const Parent = () => {
@@ -381,9 +370,7 @@ const Parent = () => {
381
370
 
382
371
  const openDetail = (id) => {
383
372
  // Push new view onto stack
384
- sliderHook.load!(
385
- <DetailComponent id={id} parentSliderFrameHook={sliderHook} />,
386
- );
373
+ sliderHook.load!(<DetailComponent id={id} parentSliderFrameHook={sliderHook} />);
387
374
  };
388
375
 
389
376
  return (
@@ -397,28 +384,17 @@ const Parent = () => {
397
384
  };
398
385
 
399
386
  // 2. Child Component (Level 2)
400
- const DetailComponent = (props: {
401
- id: number;
402
- parentSliderFrameHook: SliderFrameHookProps;
403
- }) => {
387
+ const DetailComponent = (props: { id: number; parentSliderFrameHook: SliderFrameHookProps }) => {
404
388
  // Define hook for Level 3
405
389
  const childSliderHook: SliderFrameHookProps = {};
406
390
 
407
391
  const openDeeper = () => {
408
392
  // Load Level 3 component into this component's placeholder
409
- childSliderHook.load!(
410
- <DetailComponent
411
- id={props.id + 1}
412
- parentSliderFrameHook={childSliderHook}
413
- />,
414
- );
393
+ childSliderHook.load!(<DetailComponent id={props.id + 1} parentSliderFrameHook={childSliderHook} />);
415
394
  };
416
395
 
417
396
  return (
418
- <HeaderWithBackFrame
419
- title="Detail Page"
420
- onBack={(e) => props.parentSliderFrameHook.close!(e)}
421
- >
397
+ <HeaderWithBackFrame title='Detail Page' onBack={(e) => props.parentSliderFrameHook.close!(e)}>
422
398
  {/* Placeholder for Level 3 */}
423
399
  <SliderFrame hook={childSliderHook} />
424
400
 
@@ -454,7 +430,7 @@ const Parent = () => {
454
430
  const ref: RefProps = {
455
431
  onLoad: async () => {
456
432
  // Safe: Child has rendered and populated the hook
457
- myHook.setValue("Hello");
433
+ myHook.setValue('Hello');
458
434
  },
459
435
  };
460
436
 
@@ -507,14 +483,14 @@ You can import an `.svg` file (if your bundler supports it) or define a raw Data
507
483
 
508
484
  ```typescript
509
485
  // Option A: Using bundler import
510
- import githubIcon from "github.svg";
486
+ import githubIcon from 'github.svg';
511
487
 
512
488
  // Option B: Raw Data URI string
513
489
  const closeSvgData = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M18 6L6 18M6 6l12 12'/%3E%3C/svg%3E`;
514
490
 
515
491
  export const DemoIcons = {
516
492
  github: githubIcon,
517
- "ma-close": closeSvgData,
493
+ 'ma-close': closeSvgData,
518
494
  };
519
495
  ```
520
496
 
@@ -524,9 +500,9 @@ Use the `-webkit-mask-image` and `maskImage` property wrapped in `url()` to appl
524
500
  ```typescript
525
501
  const css: CssProps = {
526
502
  // Target the specific system icon class you wish to override
527
- ".ifc-icon.ma-close": {
528
- "-webkit-mask-image": `url("${DemoIcons["ma-close"]}")`,
529
- maskImage: `url("${DemoIcons["ma-close"]}")`,
503
+ '.ifc-icon.ma-close': {
504
+ '-webkit-mask-image': `url("${DemoIcons['ma-close']}")`,
505
+ maskImage: `url("${DemoIcons['ma-close']}")`,
530
506
  // If needed, specify mask sizing properties:
531
507
  // maskRepeat: 'no-repeat',
532
508
  // maskPosition: 'center',
@@ -623,12 +599,12 @@ For interactive lists, `createDragUtil()` from `lupine.components` handles compl
623
599
  1. **Option Selection (`ActionSheetSelectPromise`)** (Replaces `confirm()` or complex choices):
624
600
 
625
601
  ```typescript
626
- import { ActionSheetSelectPromise } from "lupine.components";
602
+ import { ActionSheetSelectPromise } from 'lupine.components';
627
603
 
628
604
  const index = await ActionSheetSelectPromise({
629
- title: "Delete this saved game?", // Optional
630
- options: ["Delete", "Edit"],
631
- cancelButtonText: "Cancel",
605
+ title: 'Delete this saved game?', // Optional
606
+ options: ['Delete', 'Edit'],
607
+ cancelButtonText: 'Cancel',
632
608
  });
633
609
 
634
610
  if (index === 0) {
@@ -642,25 +618,25 @@ For interactive lists, `createDragUtil()` from `lupine.components` handles compl
642
618
  2. **Simple Messages (`ActionSheetMessagePromise`)** (Replaces `alert()`):
643
619
 
644
620
  ```typescript
645
- import { ActionSheetMessagePromise } from "lupine.components";
621
+ import { ActionSheetMessagePromise } from 'lupine.components';
646
622
 
647
623
  await ActionSheetMessagePromise({
648
- title: "Success", // Optional
649
- message: "Your profile has been saved.",
650
- closeButtonText: "OK", // Optional, defaults to a close behavior
624
+ title: 'Success', // Optional
625
+ message: 'Your profile has been saved.',
626
+ closeButtonText: 'OK', // Optional, defaults to a close behavior
651
627
  });
652
628
  ```
653
629
 
654
630
  3. **User Input (`ActionSheetInputPromise`)** (Replaces `prompt()`):
655
631
 
656
632
  ```typescript
657
- import { ActionSheetInputPromise } from "lupine.components";
633
+ import { ActionSheetInputPromise } from 'lupine.components';
658
634
 
659
635
  const value = await ActionSheetInputPromise({
660
- title: "Enter your name",
636
+ title: 'Enter your name',
661
637
  // placeholder: 'Player 1', // Optional
662
- confirmButtonText: "Submit", // Optional
663
- cancelButtonText: "Cancel", // Optional
638
+ confirmButtonText: 'Submit', // Optional
639
+ cancelButtonText: 'Cancel', // Optional
664
640
  });
665
641
 
666
642
  if (value !== null) {
@@ -680,12 +656,12 @@ When building mobile interfaces, users expect the physical hardware "Back" butto
680
656
  **The Rule**: Whenever you implement a cancel button, a close icon (`X`), or a back chevron (`<`) in a mobile overlay or frame, you **MUST** attach the `data-back-action` attribute using the `backActionHelper`.
681
657
 
682
658
  ```typescript
683
- import { backActionHelper } from "lupine.components";
659
+ import { backActionHelper } from 'lupine.components';
684
660
 
685
661
  export const MyCloseButton = ({ onClose }) => {
686
662
  return (
687
663
  <i
688
- class="ifc-icon ma-close"
664
+ class='ifc-icon ma-close'
689
665
  // Generate a unique ID for the back stack
690
666
  data-back-action={backActionHelper.genBackActionId()}
691
667
  onClick={onClose}