systemlink-cli 1.6.1__tar.gz → 1.6.2__tar.gz

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.
Files changed (76) hide show
  1. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/PKG-INFO +1 -1
  2. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/pyproject.toml +1 -1
  3. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/_version.py +1 -1
  4. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/slcli/SKILL.md +6 -5
  5. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/systemlink-webapp/SKILL.md +3 -2
  6. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/webapp_click.py +122 -25
  7. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/LICENSE +0 -0
  8. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/dff-editor/editor.js +0 -0
  9. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/dff-editor/index.html +0 -0
  10. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/__init__.py +0 -0
  11. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/__main__.py +0 -0
  12. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/asset_click.py +0 -0
  13. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/cli_formatters.py +0 -0
  14. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/cli_utils.py +0 -0
  15. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/comment_click.py +0 -0
  16. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/completion_click.py +0 -0
  17. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/config.py +0 -0
  18. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/config_click.py +0 -0
  19. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/dff_click.py +0 -0
  20. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/dff_decorators.py +0 -0
  21. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/example_click.py +0 -0
  22. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/example_loader.py +0 -0
  23. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/example_provisioner.py +0 -0
  24. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/README.md +0 -0
  25. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/_schema/schema-v1.0.json +0 -0
  26. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/demo-complete-workflow/README.md +0 -0
  27. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
  28. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/demo-test-plans/README.md +0 -0
  29. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/demo-test-plans/config.yaml +0 -0
  30. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
  31. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
  32. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
  33. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
  34. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
  35. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
  36. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
  37. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
  38. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
  39. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  40. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/feed_click.py +0 -0
  41. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/file_click.py +0 -0
  42. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/function_click.py +0 -0
  43. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/function_templates.py +0 -0
  44. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/main.py +0 -0
  45. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/mcp_click.py +0 -0
  46. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/mcp_server.py +0 -0
  47. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/notebook_click.py +0 -0
  48. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/platform.py +0 -0
  49. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/policy_click.py +0 -0
  50. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/policy_utils.py +0 -0
  51. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/profiles.py +0 -0
  52. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/response_handlers.py +0 -0
  53. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/rich_output.py +0 -0
  54. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/routine_click.py +0 -0
  55. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skill_click.py +0 -0
  56. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
  57. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/slcli/references/filtering.md +0 -0
  58. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
  59. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/systemlink-webapp/references/layout-patterns.md +0 -0
  60. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
  61. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
  62. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/ssl_trust.py +0 -0
  63. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/system_click.py +0 -0
  64. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/table_utils.py +0 -0
  65. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/tag_click.py +0 -0
  66. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/templates_click.py +0 -0
  67. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/testmonitor_click.py +0 -0
  68. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/universal_handlers.py +0 -0
  69. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/user_click.py +0 -0
  70. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/utils.py +0 -0
  71. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/web_editor.py +0 -0
  72. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/workflow_preview.py +0 -0
  73. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/workflows_click.py +0 -0
  74. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/workitem_click.py +0 -0
  75. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/workspace_click.py +0 -0
  76. {systemlink_cli-1.6.1 → systemlink_cli-1.6.2}/slcli/workspace_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: systemlink-cli
3
- Version: 1.6.1
3
+ Version: 1.6.2
4
4
  Summary: SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates.
5
5
  License-File: LICENSE
6
6
  Author: Fred Visser
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "systemlink-cli"
3
- version = "1.6.1"
3
+ version = "1.6.2"
4
4
  description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
5
5
  authors = ["Fred Visser <fred.visser@emerson.com>"]
6
6
  packages = [{ include = "slcli" }]
@@ -1,4 +1,4 @@
1
1
  """Version information for slcli."""
2
2
 
3
3
  # This file is auto-generated. Do not edit manually.
4
- __version__ = "1.6.1"
4
+ __version__ = "1.6.2"
@@ -666,7 +666,7 @@ Scaffold, package, and publish custom web applications to SystemLink.
666
666
 
667
667
  ```bash
668
668
  slcli webapp init <DIRECTORY> # Scaffold the Angular starter
669
- slcli webapp manifest init <DIRECTORY> [OPTIONS] # Create manifest.json + nipkg.config.json
669
+ slcli webapp manifest init <DIRECTORY> [OPTIONS] # Create nipkg.config.json for packaging
670
670
  slcli webapp pack [FOLDER] [--config FILE] [-o OUTPUT_FILE] # Package a webapp into a .nipkg
671
671
  slcli webapp list [-w WORKSPACE] [-t INT] [-f json]
672
672
  slcli webapp get <WEBAPP_ID> [-f json]
@@ -680,11 +680,12 @@ project-scoped skills into `.agents/skills/` and creates `PROMPTS.md` plus `STAR
680
680
  AI assistant can bootstrap the Angular workspace in place with the same Nimble/SystemLink
681
681
  conventions described by the `systemlink-webapp` skill.
682
682
 
683
- `webapp manifest init` writes `manifest.json` and `nipkg.config.json` using the Plugin Manager
684
- field names (`section`, `maintainer`, `homepage`, `xbPlugin`, `slPluginManagerTags`,
683
+ `webapp manifest init` writes `nipkg.config.json` using the Plugin Manager field names
684
+ (`section`, `maintainer`, `homepage`, `xbPlugin`, `slPluginManagerTags`,
685
685
  `slPluginManagerMinServerVersion`, `iconFile`). `webapp pack --config ...` consumes that
686
- metadata, carries the icon into the package, and writes the matching control-file fields into the
687
- generated `.nipkg`.
686
+ metadata, carries the icon into the package, writes the matching control-file fields into the
687
+ generated `.nipkg`, and emits a thin `manifest.json` with `schemaVersion`, `nipkgFile`,
688
+ `sha256`, and any configured provenance fields.
688
689
 
689
690
  ### skill — AI skill installation
690
691
 
@@ -24,8 +24,9 @@ That command lays down the SystemLink starter layer (`.agents/skills/`, `PROMPTS
24
24
  `START_HERE.md`) while Angular CLI remains responsible for generating the Angular workspace.
25
25
 
26
26
  When the user wants to package the app for Plugin Manager submission, prefer
27
- `slcli webapp manifest init <app-dir> ...` to generate `manifest.json` and `nipkg.config.json`
28
- with the current Plugin Manager field names, then use `slcli webapp pack --config ...`.
27
+ `slcli webapp manifest init <app-dir> ...` to generate `nipkg.config.json`
28
+ with the current Plugin Manager field names, then use `slcli webapp pack --config ...`
29
+ to build the `.nipkg` and generate the thin `manifest.json` with the artifact SHA-256.
29
30
 
30
31
  ---
31
32
 
@@ -4,6 +4,7 @@ Provides local scaffolding (init), packing helpers (pack), and remote
4
4
  management (list, get, delete, publish, open).
5
5
  """
6
6
 
7
+ import hashlib
7
8
  import io
8
9
  import re
9
10
  import shutil
@@ -45,6 +46,10 @@ _ALLOWED_ICON_EXTENSIONS = frozenset({".svg", ".png", ".jpg", ".jpeg", ".webp",
45
46
  _MAX_PACKAGE_LENGTH = 100
46
47
  _MAX_DISPLAY_NAME_LENGTH = 200
47
48
  _MAX_DESCRIPTION_LENGTH = 5000
49
+ _MAX_RELEASE_TAG_LENGTH = 200
50
+ _MAX_SCREENSHOT_COUNT = 3
51
+ _SOURCE_REPO_PATTERN = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$")
52
+ _SOURCE_COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$")
48
53
  _ALLOWED_PLUGIN_MANAGER_KEYS = {
49
54
  "buildCommand",
50
55
  "buildDir",
@@ -56,7 +61,11 @@ _ALLOWED_PLUGIN_MANAGER_KEYS = {
56
61
  "maintainer",
57
62
  "nipkgFile",
58
63
  "package",
64
+ "releaseTag",
59
65
  "section",
66
+ "screenshots",
67
+ "sourceCommit",
68
+ "sourceRepo",
60
69
  "slPluginManagerMinServerVersion",
61
70
  "slPluginManagerTags",
62
71
  "version",
@@ -237,6 +246,15 @@ def _copy_icon_file_to_directory(source_icon: Path, directory: Path) -> bool:
237
246
  return True
238
247
 
239
248
 
249
+ def _compute_sha256(file_path: Path) -> str:
250
+ """Return the SHA-256 checksum for a file."""
251
+ sha256_hash = hashlib.sha256()
252
+ with open(file_path, "rb") as input_file:
253
+ for chunk in iter(lambda: input_file.read(1024 * 1024), b""):
254
+ sha256_hash.update(chunk)
255
+ return sha256_hash.hexdigest()
256
+
257
+
240
258
  def _normalize_plugin_manager_metadata(raw_metadata: Dict[str, Any]) -> Dict[str, Any]:
241
259
  """Normalize legacy App Store keys to Plugin Manager keys."""
242
260
  metadata = dict(raw_metadata)
@@ -252,8 +270,8 @@ def _validate_plugin_manager_metadata(
252
270
  raw_metadata: Dict[str, Any],
253
271
  require_build_dir: bool = False,
254
272
  base_dir: Optional[Path] = None,
255
- ) -> Dict[str, str]:
256
- """Validate and normalize Plugin Manager manifest metadata."""
273
+ ) -> Dict[str, Any]:
274
+ """Validate and normalize Plugin Manager packaging metadata."""
257
275
  from urllib.parse import urlparse
258
276
 
259
277
  metadata = _normalize_plugin_manager_metadata(raw_metadata)
@@ -274,8 +292,13 @@ def _validate_plugin_manager_metadata(
274
292
  build_dir = str(metadata.get("buildDir", "")).strip()
275
293
  build_command = str(metadata.get("buildCommand", "")).strip()
276
294
  icon_file = str(metadata.get("iconFile", "")).strip()
295
+ source_repo = str(metadata.get("sourceRepo", "")).strip()
296
+ release_tag = str(metadata.get("releaseTag", "")).strip()
297
+ source_commit = str(metadata.get("sourceCommit", "")).strip()
298
+ screenshots_raw = metadata.get("screenshots")
277
299
 
278
300
  errors: List[str] = []
301
+ screenshots: List[str] = []
279
302
 
280
303
  if unexpected_keys:
281
304
  errors.append("unexpected field(s): " + ", ".join(unexpected_keys))
@@ -308,6 +331,25 @@ def _validate_plugin_manager_metadata(
308
331
  errors.append(f"xbPlugin must be one of: {', '.join(_XB_PLUGIN_VALUES)}")
309
332
  if nipkg_file and not nipkg_file.endswith(".nipkg"):
310
333
  errors.append("nipkgFile must end with .nipkg")
334
+ if source_repo and not _SOURCE_REPO_PATTERN.match(source_repo):
335
+ errors.append("sourceRepo must be in owner/name format")
336
+ if release_tag and len(release_tag) > _MAX_RELEASE_TAG_LENGTH:
337
+ errors.append(f"releaseTag must be at most {_MAX_RELEASE_TAG_LENGTH} characters")
338
+ if source_commit and not _SOURCE_COMMIT_PATTERN.match(source_commit):
339
+ errors.append("sourceCommit must be a 40-character lowercase git SHA")
340
+ if bool(source_repo) != bool(release_tag):
341
+ errors.append("sourceRepo and releaseTag must be provided together")
342
+ if screenshots_raw not in (None, ""):
343
+ if not isinstance(screenshots_raw, list):
344
+ errors.append("screenshots must be an array of filenames")
345
+ else:
346
+ screenshots = [str(item).strip() for item in screenshots_raw]
347
+ if any(not item for item in screenshots):
348
+ errors.append("screenshots entries must be non-empty strings")
349
+ if len(screenshots) > _MAX_SCREENSHOT_COUNT:
350
+ errors.append(f"screenshots must contain at most {_MAX_SCREENSHOT_COUNT} items")
351
+ if len(set(screenshots)) != len(screenshots):
352
+ errors.append("screenshots entries must be unique")
311
353
  if require_build_dir and not build_dir:
312
354
  errors.append("buildDir is required when packing from config without a folder argument")
313
355
  icon_error = _validate_icon_file_value(icon_file, base_dir)
@@ -320,7 +362,7 @@ def _validate_plugin_manager_metadata(
320
362
  click.echo(f" - {error}", err=True)
321
363
  sys.exit(ExitCodes.INVALID_INPUT)
322
364
 
323
- validated: Dict[str, str] = {
365
+ validated: Dict[str, Any] = {
324
366
  "package": package_name,
325
367
  "version": version,
326
368
  "displayName": display_name,
@@ -342,6 +384,14 @@ def _validate_plugin_manager_metadata(
342
384
  if build_command:
343
385
  validated["buildCommand"] = build_command
344
386
  validated["iconFile"] = icon_file
387
+ if source_repo:
388
+ validated["sourceRepo"] = source_repo
389
+ if release_tag:
390
+ validated["releaseTag"] = release_tag
391
+ if source_commit:
392
+ validated["sourceCommit"] = source_commit
393
+ if screenshots:
394
+ validated["screenshots"] = screenshots
345
395
 
346
396
  return validated
347
397
 
@@ -349,7 +399,7 @@ def _validate_plugin_manager_metadata(
349
399
  def _pack_folder_to_nipkg(
350
400
  folder: Path,
351
401
  output: Optional[Path] = None,
352
- metadata: Optional[Dict[str, str]] = None,
402
+ metadata: Optional[Dict[str, Any]] = None,
353
403
  icon_source: Optional[Path] = None,
354
404
  ) -> Path:
355
405
  """Pack a folder into a .nipkg (ar) file and return the output path.
@@ -530,7 +580,27 @@ def _build_angular_bootstrap_command(directory: Path) -> str:
530
580
  )
531
581
 
532
582
 
533
- def _build_webapp_manifest_and_config(
583
+ def _build_submission_manifest(
584
+ nipkg_path: Path, metadata: Optional[Dict[str, Any]] = None
585
+ ) -> Dict[str, Any]:
586
+ """Build the thin submission manifest from a packaged artifact."""
587
+ manifest: Dict[str, Any] = {
588
+ "schemaVersion": 2,
589
+ "nipkgFile": nipkg_path.name,
590
+ "sha256": _compute_sha256(nipkg_path),
591
+ }
592
+ if metadata is not None:
593
+ for key in ("sourceRepo", "releaseTag", "sourceCommit"):
594
+ value = metadata.get(key)
595
+ if value:
596
+ manifest[key] = value
597
+ screenshots = metadata.get("screenshots")
598
+ if screenshots:
599
+ manifest["screenshots"] = screenshots
600
+ return manifest
601
+
602
+
603
+ def _build_webapp_pack_config(
534
604
  package_name: str,
535
605
  version: str,
536
606
  display_name: str,
@@ -546,9 +616,12 @@ def _build_webapp_manifest_and_config(
546
616
  build_command: str,
547
617
  icon_file: str,
548
618
  icon_validation_base_dir: Path,
549
- ) -> tuple[Dict[str, str], Dict[str, str]]:
550
- """Build validated submission manifest and nipkg config payloads."""
551
- manifest = _validate_plugin_manager_metadata(
619
+ source_repo: str,
620
+ release_tag: str,
621
+ source_commit: str,
622
+ ) -> Dict[str, Any]:
623
+ """Build validated nipkg.config.json payloads."""
624
+ pack_config = _validate_plugin_manager_metadata(
552
625
  {
553
626
  "package": package_name,
554
627
  "version": version,
@@ -561,18 +634,19 @@ def _build_webapp_manifest_and_config(
561
634
  "xbPlugin": xb_plugin,
562
635
  "slPluginManagerTags": tags,
563
636
  "slPluginManagerMinServerVersion": min_server_version,
564
- "nipkgFile": _default_nipkg_filename(package_name, version),
565
637
  "iconFile": icon_file,
638
+ "sourceRepo": source_repo,
639
+ "releaseTag": release_tag,
640
+ "sourceCommit": source_commit,
566
641
  },
567
642
  base_dir=icon_validation_base_dir,
568
643
  )
569
644
 
570
- pack_config = dict(manifest)
571
645
  pack_config.pop("nipkgFile", None)
572
646
  pack_config["buildDir"] = build_dir
573
647
  pack_config["buildCommand"] = build_command
574
648
 
575
- return manifest, pack_config
649
+ return pack_config
576
650
 
577
651
 
578
652
  def _render_angular_prompts_md(directory: Path) -> str:
@@ -658,7 +732,7 @@ This directory was initialized with `slcli webapp init`.
658
732
  - bundled AI skills in `.agents/skills/`
659
733
  - ready-made prompts in [PROMPTS.md](PROMPTS.md)
660
734
  - deployment guidance for `slcli webapp publish`
661
- - Plugin Manager manifest scaffolding via `slcli webapp manifest init`
735
+ - Plugin Manager packaging config scaffolding via `slcli webapp manifest init`
662
736
 
663
737
  Angular CLI remains the source of truth for the Angular workspace itself. That
664
738
  keeps the generated project aligned with current Angular defaults while the
@@ -774,7 +848,7 @@ def register_webapp_commands(cli: Any) -> None:
774
848
 
775
849
  @webapp.group(name="manifest")
776
850
  def webapp_manifest() -> None:
777
- """Create Plugin Manager submission manifests and packaging config files."""
851
+ """Create Plugin Manager packaging config and submission manifest inputs."""
778
852
 
779
853
  @webapp_manifest.command(name="init")
780
854
  @click.argument(
@@ -810,12 +884,27 @@ def register_webapp_commands(cli: Any) -> None:
810
884
  show_default=True,
811
885
  help="Build command written to nipkg.config.json",
812
886
  )
887
+ @click.option(
888
+ "--source-repo",
889
+ default="",
890
+ help="Optional provenance repository in owner/name format for the generated manifest",
891
+ )
892
+ @click.option(
893
+ "--release-tag",
894
+ default="",
895
+ help="Optional provenance release tag for the generated manifest",
896
+ )
897
+ @click.option(
898
+ "--source-commit",
899
+ default="",
900
+ help="Optional source commit SHA for the generated manifest",
901
+ )
813
902
  @click.option(
814
903
  "--icon-file",
815
904
  required=True,
816
905
  help="Path to the icon asset; copied into the manifest directory as iconFile",
817
906
  )
818
- @click.option("--force", is_flag=True, help="Overwrite existing manifest files")
907
+ @click.option("--force", is_flag=True, help="Overwrite existing packaging files")
819
908
  def init_manifest(
820
909
  directory: Path,
821
910
  package_name: str,
@@ -831,10 +920,13 @@ def register_webapp_commands(cli: Any) -> None:
831
920
  min_server_version: str,
832
921
  build_dir: str,
833
922
  build_command: str,
923
+ source_repo: str,
924
+ release_tag: str,
925
+ source_commit: str,
834
926
  icon_file: str,
835
927
  force: bool,
836
928
  ) -> None:
837
- """Write manifest.json and nipkg.config.json using the Plugin Manager contract."""
929
+ """Write nipkg.config.json for Plugin Manager packaging."""
838
930
  try:
839
931
  directory.mkdir(parents=True, exist_ok=True)
840
932
 
@@ -842,11 +934,8 @@ def register_webapp_commands(cli: Any) -> None:
842
934
  display_name = display_name or _default_display_name(package_name)
843
935
  build_dir = build_dir or _default_angular_build_dir(directory)
844
936
 
845
- manifest_path = directory / "manifest.json"
846
937
  config_path = directory / "nipkg.config.json"
847
938
  existing = []
848
- if manifest_path.exists() and not force:
849
- existing.append("manifest.json")
850
939
  if config_path.exists() and not force:
851
940
  existing.append("nipkg.config.json")
852
941
  if existing:
@@ -860,7 +949,7 @@ def register_webapp_commands(cli: Any) -> None:
860
949
  icon_file, directory, force
861
950
  )
862
951
 
863
- manifest, pack_config = _build_webapp_manifest_and_config(
952
+ pack_config = _build_webapp_pack_config(
864
953
  package_name=package_name,
865
954
  version=version,
866
955
  display_name=display_name,
@@ -876,11 +965,13 @@ def register_webapp_commands(cli: Any) -> None:
876
965
  build_command=build_command,
877
966
  icon_file=manifest_icon_file,
878
967
  icon_validation_base_dir=source_icon.parent,
968
+ source_repo=source_repo,
969
+ release_tag=release_tag,
970
+ source_commit=source_commit,
879
971
  )
880
972
 
881
973
  copied_icon = _copy_icon_file_to_directory(source_icon, directory)
882
974
  try:
883
- save_json_file(manifest, str(manifest_path))
884
975
  save_json_file(pack_config, str(config_path))
885
976
  except Exception:
886
977
  if copied_icon:
@@ -888,11 +979,10 @@ def register_webapp_commands(cli: Any) -> None:
888
979
  raise
889
980
 
890
981
  format_success(
891
- "Created Plugin Manager manifest files",
982
+ "Created Plugin Manager pack config",
892
983
  {
893
- "Manifest": str(manifest_path),
894
984
  "Pack config": str(config_path),
895
- "nipkgFile": manifest["nipkgFile"],
985
+ "Next step": "Run slcli webapp pack --config nipkg.config.json to generate the .nipkg and manifest.json",
896
986
  },
897
987
  )
898
988
  except SystemExit:
@@ -928,7 +1018,7 @@ def register_webapp_commands(cli: Any) -> None:
928
1018
  ) -> None:
929
1019
  """Pack a folder into a .nipkg."""
930
1020
  try:
931
- metadata: Optional[Dict[str, str]] = None
1021
+ metadata: Optional[Dict[str, Any]] = None
932
1022
  resolved_folder = folder
933
1023
 
934
1024
  if config_path is not None:
@@ -969,7 +1059,14 @@ def register_webapp_commands(cli: Any) -> None:
969
1059
  icon_source = _resolve_local_path(metadata["iconFile"], config_path.parent)
970
1060
 
971
1061
  result = _pack_folder_to_nipkg(resolved_folder, out, metadata, icon_source)
972
- format_success("Packed folder", {"Path": str(result)})
1062
+ success_data: Dict[str, str] = {"Path": str(result)}
1063
+ if metadata is not None and config_path is not None:
1064
+ manifest_path = config_path.parent / "manifest.json"
1065
+ submission_manifest = _build_submission_manifest(result, metadata)
1066
+ save_json_file(submission_manifest, str(manifest_path))
1067
+ success_data["Manifest"] = str(manifest_path)
1068
+
1069
+ format_success("Packed folder", success_data)
973
1070
  except SystemExit:
974
1071
  raise
975
1072
  except Exception as exc:
File without changes