cistack 6.1.0 → 6.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cistack",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "description": "Automatically generate GitHub Actions CI/CD pipelines by analysing your codebase",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -1,10 +1,11 @@
1
1
  # Generated by cistack v6.0.0 — https://github.com/cistack
2
2
  # Unified Pipeline: ci, deploy
3
+ # CI: locale JSON files are checked for key parity vs en.json (scripts/validate-i18n.mjs, no install).
3
4
  # Required secrets: VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID
4
5
  # Add these at: Settings → Secrets and Variables → Actions
5
6
  # Dependabot remains in .github/dependabot.yml
6
7
 
7
- name: Pipeline
8
+ name: CISTACK PIPELINE
8
9
  'on':
9
10
  push:
10
11
  branches:
@@ -17,6 +18,17 @@ concurrency:
17
18
  group: ${{ github.workflow }}-${{ github.ref }}
18
19
  cancel-in-progress: true
19
20
  jobs:
21
+ ci_i18n:
22
+ name: 🌐 Locale strings
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - name: Checkout code
26
+ uses: actions/checkout@v4
27
+ - name: Validate dictionary key parity
28
+ run: node scripts/validate-i18n.mjs
29
+ if: >-
30
+ (github.event_name == 'push' && (github.ref_name == 'main')) || (github.event_name == 'pull_request' &&
31
+ (github.base_ref == 'main'))
20
32
  ci_lint:
21
33
  name: 🔍 Lint & Format
22
34
  runs-on: ubuntu-latest
@@ -44,6 +56,7 @@ jobs:
44
56
  runs-on: ubuntu-latest
45
57
  needs:
46
58
  - ci_lint
59
+ - ci_i18n
47
60
  steps:
48
61
  - name: Checkout code
49
62
  uses: actions/checkout@v4
@@ -1,33 +1,27 @@
1
1
  "use client";
2
2
 
3
+ import { AnimatePresence, m, useReducedMotion } from "framer-motion";
4
+ import { Check, Copy } from "lucide-react";
3
5
  import { useEffect, useRef, useState } from "react";
4
6
 
5
- type CopyButtonVariant = "hero" | "terminal";
7
+ const iconClass = "h-4 w-4 shrink-0";
6
8
 
7
9
  interface CopyButtonProps {
8
10
  text: string;
9
- variant?: CopyButtonVariant;
10
11
  className?: string;
12
+ idleLabel?: string;
13
+ successLabel?: string;
11
14
  }
12
15
 
13
- const copyLabels: Record<CopyButtonVariant, { idle: string; success: string }> = {
14
- hero: {
15
- idle: "COPY",
16
- success: "COPIED",
17
- },
18
- terminal: {
19
- idle: "Copy",
20
- success: "Copied",
21
- },
22
- };
23
-
24
16
  export default function CopyButton({
25
17
  text,
26
- variant = "hero",
27
18
  className = "",
19
+ idleLabel = "Copy",
20
+ successLabel = "Copied",
28
21
  }: CopyButtonProps) {
29
22
  const [copied, setCopied] = useState(false);
30
23
  const timeoutRef = useRef<number | null>(null);
24
+ const reduce = useReducedMotion();
31
25
 
32
26
  useEffect(() => {
33
27
  return () => {
@@ -41,45 +35,67 @@ export default function CopyButton({
41
35
  try {
42
36
  await navigator.clipboard.writeText(text);
43
37
  setCopied(true);
44
-
45
38
  if (timeoutRef.current !== null) {
46
39
  window.clearTimeout(timeoutRef.current);
47
40
  }
48
-
49
41
  timeoutRef.current = window.setTimeout(() => {
50
42
  setCopied(false);
51
43
  }, 2000);
52
44
  } catch (error) {
53
- console.error("Unable to copy command", error);
45
+ console.error("Unable to copy text", error);
54
46
  }
55
47
  };
56
48
 
57
- if (variant === "terminal") {
58
- return (
59
- <button
60
- type="button"
61
- onClick={handleCopy}
62
- className={`text-[12px] font-semibold text-zinc-600 transition-colors hover:text-zinc-900 ${className}`}
63
- aria-label="Copy command"
64
- >
65
- {copied ? copyLabels.terminal.success : copyLabels.terminal.idle}
66
- </button>
67
- );
68
- }
49
+ const label = copied ? successLabel : idleLabel;
50
+ const tap = reduce ? {} : { scale: 0.92 };
51
+ const hover = reduce ? {} : { scale: 1.06 };
69
52
 
70
53
  return (
71
- <button
54
+ <m.button
72
55
  type="button"
73
56
  onClick={handleCopy}
74
- className={`flex h-auto items-center justify-between gap-6 rounded-sm border border-zinc-800 bg-zinc-950 px-6 py-3.5 text-[14px] text-white shadow-[0_8px_30px_rgb(0,0,0,0.12)] transition-all hover:bg-black hover:shadow-zinc-300/50 ${className}`}
75
- aria-label="Copy install command"
57
+ whileTap={tap}
58
+ whileHover={hover}
59
+ transition={{ type: "spring", stiffness: 520, damping: 28 }}
60
+ className={`flex h-9 w-9 shrink-0 items-center justify-center text-zinc-500 transition-colors hover:text-zinc-900 ${className}`}
61
+ aria-label={label}
62
+ title={label}
76
63
  >
77
- <span className="font-mono font-bold tracking-tight text-emerald-400 transition-colors">
78
- {text}
79
- </span>
80
- <span className="ml-2 border-l border-zinc-800 pl-6 text-[12px] font-black uppercase tracking-[0.18em] text-zinc-300">
81
- {copied ? copyLabels.hero.success : copyLabels.hero.idle}
64
+ <span className="relative flex h-4 w-4 items-center justify-center">
65
+ {reduce ? (
66
+ copied ? (
67
+ <Check className={`${iconClass} text-emerald-600`} aria-hidden strokeWidth={2.25} />
68
+ ) : (
69
+ <Copy className={iconClass} aria-hidden strokeWidth={2} />
70
+ )
71
+ ) : (
72
+ <AnimatePresence mode="wait" initial={false}>
73
+ {copied ? (
74
+ <m.span
75
+ key="check"
76
+ initial={{ opacity: 0, scale: 0.35, rotate: -50 }}
77
+ animate={{ opacity: 1, scale: 1, rotate: 0 }}
78
+ exit={{ opacity: 0, scale: 0.75 }}
79
+ transition={{ type: "spring", stiffness: 420, damping: 22 }}
80
+ className="absolute inset-0 flex items-center justify-center text-emerald-600"
81
+ >
82
+ <Check className={iconClass} aria-hidden strokeWidth={2.25} />
83
+ </m.span>
84
+ ) : (
85
+ <m.span
86
+ key="copy"
87
+ initial={{ opacity: 0, scale: 0.75 }}
88
+ animate={{ opacity: 1, scale: 1 }}
89
+ exit={{ opacity: 0, scale: 0.8 }}
90
+ transition={{ duration: 0.15, ease: [0.22, 1, 0.36, 1] }}
91
+ className="absolute inset-0 flex items-center justify-center"
92
+ >
93
+ <Copy className={iconClass} aria-hidden strokeWidth={2} />
94
+ </m.span>
95
+ )}
96
+ </AnimatePresence>
97
+ )}
82
98
  </span>
83
- </button>
99
+ </m.button>
84
100
  );
85
101
  }