afterpython 0.1.4__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
@@ -190,6 +190,7 @@ def build(ctx: click.Context, execute: bool):
190
190
  find_node_env,
191
191
  handle_passthrough_help,
192
192
  has_content_for_myst,
193
+ is_website_initialized,
193
194
  )
194
195
 
195
196
  # Show both our options and myst's help and exit
@@ -199,6 +200,13 @@ def build(ctx: click.Context, execute: bool):
199
200
  show_underlying=True,
200
201
  )
201
202
 
203
+ if not is_website_initialized():
204
+ click.echo(
205
+ "Website has not been initialized. Skipping website build.\n"
206
+ "Run 'ap update website' to initialize the website."
207
+ )
208
+ return
209
+
202
210
  paths = ctx.obj["paths"]
203
211
  prebuild()
204
212
 
@@ -94,7 +94,7 @@ def dev(
94
94
  with custom Vite options in the afterpython/_website directory.
95
95
  """
96
96
 
97
- from afterpython.utils import handle_passthrough_help
97
+ from afterpython.utils import handle_passthrough_help, is_website_initialized
98
98
 
99
99
  # Show both our options and myst's help and exit
100
100
  handle_passthrough_help(
@@ -103,6 +103,13 @@ def dev(
103
103
  show_underlying=True,
104
104
  )
105
105
 
106
+ if not is_website_initialized():
107
+ click.echo(
108
+ "Website has not been initialized. Skipping dev server.\n"
109
+ "Run 'ap update website' to initialize the website."
110
+ )
111
+ return
112
+
106
113
  # Track all MyST processes for cleanup
107
114
  myst_processes = []
108
115
 
@@ -46,8 +46,13 @@ def init_py_typed():
46
46
  is_flag=True,
47
47
  help="Automatically answer yes to all prompts",
48
48
  )
49
+ @click.option(
50
+ "--skip-website",
51
+ is_flag=True,
52
+ help="Skip website template initialization",
53
+ )
49
54
  @click.pass_context
50
- def init(ctx, yes):
55
+ def init(ctx, yes, skip_website: bool):
51
56
  """Initialize AfterPython project structure and website template"""
52
57
  from afterpython.tools._afterpython import init_afterpython
53
58
  from afterpython.tools.commitizen import init_commitizen
@@ -73,9 +78,9 @@ def init(ctx, yes):
73
78
 
74
79
  init_myst()
75
80
 
76
- # TODO: init faq.yml
77
-
78
- init_website()
81
+ if not skip_website:
82
+ # TODO: init faq.yml
83
+ init_website()
79
84
 
80
85
  # TODO: add type checking related stuff here
81
86
  init_py_typed()
@@ -8,7 +8,14 @@ from click.exceptions import Exit
8
8
  @click.pass_context
9
9
  def preview(ctx):
10
10
  """Preview the production build of the project website"""
11
- from afterpython.utils import find_node_env
11
+ from afterpython.utils import find_node_env, is_website_initialized
12
+
13
+ if not is_website_initialized():
14
+ click.echo(
15
+ "Website has not been initialized. Skipping preview.\n"
16
+ "Run 'ap update website' to initialize the website."
17
+ )
18
+ return
12
19
 
13
20
  paths = ctx.obj["paths"]
14
21
  node_env = find_node_env()
@@ -126,7 +126,7 @@ update.add_command(dependencies, name="deps") # alias for "dependencies"
126
126
  )
127
127
  def website(ctx, no_backup: bool):
128
128
  """Update project website template to the latest version"""
129
- from afterpython.utils import find_node_env
129
+ from afterpython.utils import ensure_website_gitignore_rules, find_node_env
130
130
 
131
131
  website_template_repo = "AfterPythonOrg/project-website-template"
132
132
 
@@ -166,6 +166,9 @@ def website(ctx, no_backup: bool):
166
166
  )
167
167
  if result.returncode != 0:
168
168
  raise Exit(result.returncode)
169
+
170
+ # Ensure gitignore rules are present
171
+ ensure_website_gitignore_rules()
169
172
  except Exception as e:
170
173
  click.echo(f"✗ Error updating project website template: {e}", err=True)
171
174
  if not no_backup:
afterpython/py.typed ADDED
File without changes
@@ -33,7 +33,7 @@ jobs:
33
33
  - name: Install dependencies
34
34
  # Install the package with uv in system mode
35
35
  # This installs ruff and other tools defined in dependencies
36
- run: uv pip install --system .
36
+ run: uv pip install --system . --group dev
37
37
 
38
38
  - name: Run ruff check
39
39
  run: ruff check --config ./afterpython/ruff.toml .
@@ -189,9 +189,18 @@ jobs:
189
189
  run: uv pip install --system dist/*.whl
190
190
 
191
191
  - name: Verify installation
192
- # Confirm the CLI entry point works
193
- # This ensures the package is usable after installation
194
- run: ap --help
192
+ # Generic verification: assume project.name maps to import name
193
+ # by replacing '-' with '_'. If a project breaks this rule, CI fails.
194
+ run: |
195
+ PROJECT_NAME="$(python - <<'PY'
196
+ import tomllib
197
+ with open("pyproject.toml", "rb") as f:
198
+ data = tomllib.load(f)
199
+ print(data["project"]["name"])
200
+ PY
201
+ )"
202
+ IMPORT_NAME="${PROJECT_NAME//-/_}"
203
+ python -c "import ${IMPORT_NAME}"
195
204
 
196
205
  # Job 5: Type Check
197
206
  # typecheck:
afterpython/utils.py CHANGED
@@ -297,3 +297,69 @@ def get_relative_static_prefix(file_path: Path, content_type_path: Path) -> str:
297
297
  return "./" # File is directly in content_type_path
298
298
  else:
299
299
  return "../" * depth
300
+
301
+
302
+ def is_website_initialized() -> bool:
303
+ """Check if the website has been initialized.
304
+
305
+ Returns:
306
+ True if the website is initialized (afterpython/_website exists and has package.json)
307
+ False otherwise
308
+ """
309
+ import afterpython as ap
310
+
311
+ website_path = ap.paths.website_path
312
+ package_json = website_path / "package.json"
313
+
314
+ return website_path.exists() and package_json.exists()
315
+
316
+
317
+ def ensure_website_gitignore_rules() -> None:
318
+ """Ensure website-related gitignore rules are present in the project's .gitignore.
319
+
320
+ Adds the following rules if they don't exist:
321
+ - **/_build/ (ignore all build directories)
322
+ - !afterpython/_website/src/lib/ (un-ignore website source for customization)
323
+ - afterpython/_website.backup/ (ignore backup created by ap update website)
324
+ """
325
+ import afterpython as ap
326
+
327
+ gitignore_path = ap.paths.user_path / ".gitignore"
328
+
329
+ # Rules that should be present
330
+ required_rules = [
331
+ "**/_build/",
332
+ "!afterpython/_website/src/lib/",
333
+ "afterpython/_website.backup/",
334
+ ]
335
+
336
+ # Read existing gitignore or create empty list
337
+ if gitignore_path.exists():
338
+ existing_lines = gitignore_path.read_text().splitlines()
339
+ else:
340
+ existing_lines = []
341
+
342
+ # Find which rules are missing
343
+ missing_rules = []
344
+ for rule in required_rules:
345
+ # Check if rule exists (exact match or as part of a line)
346
+ if not any(rule in line for line in existing_lines):
347
+ missing_rules.append(rule)
348
+
349
+ # Add missing rules if any
350
+ if missing_rules:
351
+ # Ensure file ends with newline if it exists and has content
352
+ content = gitignore_path.read_text() if gitignore_path.exists() else ""
353
+ if content and not content.endswith("\n"):
354
+ content += "\n"
355
+
356
+ # Add a section header if we're adding rules
357
+ if content:
358
+ content += "\n"
359
+ content += "# AfterPython website rules\n"
360
+ content += "\n".join(missing_rules) + "\n"
361
+
362
+ gitignore_path.write_text(content)
363
+ click.echo(f"Added website-related rules to {gitignore_path}")
364
+ else:
365
+ click.echo("Website gitignore rules already present")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.1.4
3
+ Version: 0.2.1
4
4
  Summary: CLI tool to streamline Python package maintenance and generate a dedicated project website
5
5
  Keywords: package-maintenance,project-website,static-site-generator,developer-tools,documentation,cli-tool,ci/cd,mystmd,semantic-release,python-packaging
6
6
  Author: Stephen Yau
@@ -11,30 +11,31 @@ afterpython/builders/markdown.py,sha256=0ExfRqvOTWsgUkAVaGGNkiNDtNedRr7JR1yTGxpb
11
11
  afterpython/builders/metadata.py,sha256=--0zWaI0ZI1wKeezcyqSUy-h1IuLqnI33ateoAHIaBU,1337
12
12
  afterpython/cli/__init__.py,sha256=G4AB-rllhmr7tURFc5cXJfkjZZ2bXtMBg3oy4zUGEFw,84
13
13
  afterpython/cli/commands/add.py,sha256=Ppp4msdvOwN9_-fdG5-b0-IV9p0Eef9CiIEU3srUjYk,1792
14
- afterpython/cli/commands/build.py,sha256=Vvyd7AkL2Tw3vEDhi4bEOWD9hdtxon7SgAzXUeh5D8w,9050
14
+ afterpython/cli/commands/build.py,sha256=HR2x7tn12qbsrSk--0Mzbnl4UbNU_RhexG_6I2mxF04,9304
15
15
  afterpython/cli/commands/bump.py,sha256=2MZBQ1kxjr0bWhA--_xHwdwjsUlCyrXPn3pJ5NDZO4k,4088
16
16
  afterpython/cli/commands/check.py,sha256=lrPaVThAwxxnczu-VOZT7dd7aKjstnMQfX9UpZY3Lxs,1058
17
17
  afterpython/cli/commands/clean.py,sha256=havP43ZC1AKFzvscsRMF_to6TvOwruxGA5qzr6dMHvg,2529
18
18
  afterpython/cli/commands/commit.py,sha256=9QOKyt4tfTJzZEdHEPAdpUc4stYMSvG3-31uADuBxn4,1420
19
19
  afterpython/cli/commands/commitizen.py,sha256=H3jewz4VkZi5FYTWw3USVM3XosRx12kPShOQGxD3LNM,842
20
- afterpython/cli/commands/dev.py,sha256=YIJECfroGRQnuMWWRC4W_DMA0eJoQssufj8WBDjQVOM,6796
20
+ afterpython/cli/commands/dev.py,sha256=LVv6WUB1RNe8w7yAQS_1TQEEn_wklbJOuduapYlg2FA,7039
21
21
  afterpython/cli/commands/format.py,sha256=VFwCG9KDTF6j4Y_Ewp4_RpS8uNBQNOAj9HU_G97my5A,710
22
- afterpython/cli/commands/init.py,sha256=UiE8QIhDV9NqoqU2EU_FMAlJ2Y18lFF0x2JmcyiAQxc,3044
22
+ afterpython/cli/commands/init.py,sha256=G2zL3W1ZxbppMQEYCD7zttNIcrhAG8K2M1gZpcL4Ebo,3202
23
23
  afterpython/cli/commands/init_branch_rules.py,sha256=fzAm8D5qbW3u4bEMpqPuRgO-0akuCGG7pu09JlKhYn4,243
24
24
  afterpython/cli/commands/install.py,sha256=B8JuQmtU_4EZpHct2J5j30BGLkGmvwKvU26upbmabvQ,750
25
25
  afterpython/cli/commands/lock.py,sha256=Kppu_0tnqdbP6Gbulxf123f-cFzTGIw39si2O7rDo3Q,603
26
26
  afterpython/cli/commands/pre_commit.py,sha256=QIuzTZEfw4uPMqmQT8YmyD0yewBcwjLvcnbVjRbonQE,1978
27
- afterpython/cli/commands/preview.py,sha256=sBwe46H0V-U7I4d0-u-nXmPBWGmkjKhkUTbqV927zRM,593
27
+ afterpython/cli/commands/preview.py,sha256=V3gzAYcWi95A9GUwqcQudgtSMFcKHVJPz3l4grg5vYg,833
28
28
  afterpython/cli/commands/release.py,sha256=6SDEnPtuCL0_XUqWUlvRSKxAv011QqOD0kc5pKu7378,2479
29
29
  afterpython/cli/commands/remove.py,sha256=Fl79JSI9IdHyzYflocXxcxgBkBPkNFAW98Yuijly26g,1812
30
30
  afterpython/cli/commands/start.py,sha256=q4rUkIllOO8wJQ9ABtsx-dqkW1yC0vltAtSkiHN_GH4,4002
31
31
  afterpython/cli/commands/sync.py,sha256=u9L3aq04M5VHh0FByDSpmBD7F1RYCNsIgvZFifnRZ6A,6042
32
- afterpython/cli/commands/update.py,sha256=4h_zKp72e4RmXUKHyeP6kfHqr4V3IugSOnxa-E1F55I,6312
32
+ afterpython/cli/commands/update.py,sha256=YhCOBTGoKkYMnUK4qg-DX-FOmq1hPNSD53_IllpnUrU,6431
33
33
  afterpython/cli/main.py,sha256=m3BJ5rOlllUyOngD2HF2fosDLrduREzAQKpsTqcYPZc,4956
34
34
  afterpython/const.py,sha256=B_yUBmHPyu98aOibwt7idWbhKNBBed0TeYXNBd8tPTk,157
35
35
  afterpython/main.py,sha256=w0z6mTib3u4p2HxMrk-tIJlZtur7uRaqsbE0ewlYlSs,490
36
36
  afterpython/pcu.py,sha256=EoEhSzFZ49b5qc9KH2RLxIHSzY8YFM5uhtw07chcrr8,6093
37
- afterpython/templates/ci-workflow-template.yml,sha256=8JzGBSDKwiNgk1ygEWQzc5AoLGv2RpX6G6VuZcWFZH4,5933
37
+ afterpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ afterpython/templates/ci-workflow-template.yml,sha256=4wnH1_a7puUV1loqMZhTT5xlKeGJ5aeercu0_vtnbDU,6289
38
39
  afterpython/templates/cz-template.toml,sha256=2qwbQLb0nbZaA63oWXFFzvzPNBsFZHATL1C2rdNdjjs,185
39
40
  afterpython/templates/dependabot-template.yml,sha256=CBQEgh8EsXw2X7IqzGtFI7lVT9liF6PW4DvGLB0X2LY,1136
40
41
  afterpython/templates/deploy-workflow-template.yml,sha256=2W4K3lKMYjSTPrlXIsMweBzqP9IoLtZXAAcUu5FA5NA,2365
@@ -49,9 +50,9 @@ afterpython/tools/github_actions.py,sha256=zVmg9aqUg-EO0CnbRHl9i8TmFtXpt7nd0Vjzc
49
50
  afterpython/tools/myst.py,sha256=2VO-gD25PbGSD8-v1sLI1v3Q9loahRd3ednKrW4_Utc,8113
50
51
  afterpython/tools/pre_commit.py,sha256=oNsrUkPI13rSSBSyhyTTKPnQGhDxLIPYhxu6kL0fDmo,1206
51
52
  afterpython/tools/pyproject.py,sha256=kb7BDn24yH_xS2F5_L0dK-GL4KgOJS-MIIUQhHnLorI,3507
52
- afterpython/utils.py,sha256=s1FIJP6FvpAcK8SujB8glFbiKx-ZCltywh5ihaOhL0U,9516
53
+ afterpython/utils.py,sha256=fVa3jIzCvhn7QA7N0M6d_pvojpPFrN-hae8trUYhPKU,11701
53
54
  afterpython/version.py,sha256=hihRV1S-gc1m46OaXD32ikkEkqzs-Gtq7AcAqOQV-wY,1603
54
- afterpython-0.1.4.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
55
- afterpython-0.1.4.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
56
- afterpython-0.1.4.dist-info/METADATA,sha256=5gwp33Ye7IdvmcAtZeW6Ea2unLpHcZqH-gz9Sh_zaeM,5780
57
- afterpython-0.1.4.dist-info/RECORD,,
55
+ afterpython-0.2.1.dist-info/WHEEL,sha256=iHtWm8nRfs0VRdCYVXocAWFW8ppjHL-uTJkAdZJKOBM,80
56
+ afterpython-0.2.1.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
57
+ afterpython-0.2.1.dist-info/METADATA,sha256=bYFDNElkzZ6Y4CHQCUJVCxF3CPzm885yD2tgkQoStyU,5780
58
+ afterpython-0.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.26
2
+ Generator: uv 0.9.30
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any