1- from ..utils import with_auth
1+ import os
2+ import re
3+ import shutil
4+ import tempfile
5+ import urllib .request
6+ import zipfile
7+
28import click
39
10+ from ..utils import with_auth
11+
412
513@click .command ()
614@click .argument ("app-name" , type = str , required = True )
7- @click .option ("--crawl" , type = str , required = True , help = "A URL which should be crawled by AI to generate the app." )
8- @click .option ("--prompt" , type = str , required = True , help = "A prompt to guide the AI in generating the app." )
15+ @click .option ("--crawl" , type = str , required = False , help = "A URL which should be crawled by AI to generate the app." )
16+ @click .option ("--prompt" , type = str , required = False , help = "A prompt to guide the AI in generating the app." )
917@with_auth
10- def create (api_key : str , app_name : str , crawl : str , prompt : str ) -> None :
18+ def create (app_name : str , crawl : str , prompt : str , api_key : str ) -> None :
1119 """
12- The dev command create a temporary private app in a Stacksync workspace,
13- runs a Stacksync application on localhost,
14- and uses the local bridge to route traffic to the application.
20+ Create a new app folder in the current directory from the connector template.
1521 """
16- print ("Oh boy" , api_key )
22+ target_dir = os .path .join (os .getcwd (), _to_kebab_case (app_name ))
23+ if os .path .exists (target_dir ):
24+ raise click .ClickException (
25+ f"Destination already exists: { os .path .basename (target_dir )!r} "
26+ )
27+
28+ temp_folder = _get_templates_folder ()
29+ template_dir = _get_connector_template_dir (temp_folder )
30+
31+ try :
32+ shutil .copytree (template_dir , target_dir )
33+ finally :
34+ shutil .rmtree (temp_folder , ignore_errors = True )
35+
36+ click .secho (f"Created app in { target_dir } " , fg = "green" )
37+
38+
39+ def _to_kebab_case (value : str ) -> str :
40+ normalized = re .sub (r"([a-z0-9])([A-Z])" , r"\1-\2" , value .strip ())
41+ normalized = re .sub (r"[^A-Za-z0-9]+" , "-" , normalized )
42+ normalized = normalized .strip ("-" ).lower ()
43+ if not normalized :
44+ raise click .ClickException ("App name must contain at least one letter or number." )
45+ return normalized
46+
47+
48+ def _get_templates_folder () -> str :
49+ """
50+ This function downloads a zip from Github and extracts it to a temporary folder.
51+ """
52+ temp_folder = tempfile .mkdtemp ()
53+ zip_url = "https://github.com/stacksyncdata/cdk/archive/refs/heads/prod.zip"
54+ zip_path = os .path .join (temp_folder , "cdk.zip" )
55+ with urllib .request .urlopen (zip_url ) as response , open (zip_path , "wb" ) as out_file :
56+ shutil .copyfileobj (response , out_file )
57+ with zipfile .ZipFile (zip_path , "r" ) as zip_ref :
58+ zip_ref .extractall (temp_folder )
59+ return temp_folder
60+
61+
62+ def _get_connector_template_dir (temp_folder : str ) -> str :
63+ extracted_root = os .path .join (temp_folder , "cdk-prod" )
64+ template_dir = os .path .join (extracted_root , "templates" , "connector" )
65+ if not os .path .isdir (template_dir ):
66+ raise click .ClickException (
67+ "Failed to locate the connector template in the downloaded archive."
68+ )
69+ return template_dir
0 commit comments