napt 0.3.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.
- napt/__init__.py +91 -0
- napt/build/__init__.py +47 -0
- napt/build/manager.py +1087 -0
- napt/build/packager.py +315 -0
- napt/build/template.py +301 -0
- napt/cli.py +602 -0
- napt/config/__init__.py +42 -0
- napt/config/loader.py +465 -0
- napt/core.py +385 -0
- napt/detection.py +630 -0
- napt/discovery/__init__.py +86 -0
- napt/discovery/api_github.py +445 -0
- napt/discovery/api_json.py +452 -0
- napt/discovery/base.py +244 -0
- napt/discovery/url_download.py +304 -0
- napt/discovery/web_scrape.py +467 -0
- napt/exceptions.py +149 -0
- napt/io/__init__.py +42 -0
- napt/io/download.py +357 -0
- napt/io/upload.py +37 -0
- napt/logging.py +230 -0
- napt/policy/__init__.py +50 -0
- napt/policy/updates.py +126 -0
- napt/psadt/__init__.py +43 -0
- napt/psadt/release.py +309 -0
- napt/requirements.py +566 -0
- napt/results.py +143 -0
- napt/state/__init__.py +58 -0
- napt/state/tracker.py +371 -0
- napt/validation.py +467 -0
- napt/versioning/__init__.py +115 -0
- napt/versioning/keys.py +309 -0
- napt/versioning/msi.py +725 -0
- napt-0.3.1.dist-info/METADATA +114 -0
- napt-0.3.1.dist-info/RECORD +38 -0
- napt-0.3.1.dist-info/WHEEL +4 -0
- napt-0.3.1.dist-info/entry_points.txt +3 -0
- napt-0.3.1.dist-info/licenses/LICENSE +202 -0
napt/cli.py
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
# Copyright 2025 Roger Cibrian
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Command-line interface for NAPT.
|
|
16
|
+
|
|
17
|
+
This module provides the main CLI entry point for the napt tool, offering
|
|
18
|
+
commands for recipe validation, package building, and deployment management.
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
|
|
22
|
+
validate: Validate recipe syntax and configuration
|
|
23
|
+
discover: Discover latest version and download installer
|
|
24
|
+
build: Build PSADT package from recipe
|
|
25
|
+
package: Create .intunewin package for Intune
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
Validate recipe syntax:
|
|
29
|
+
```bash
|
|
30
|
+
$ napt validate recipes/Google/chrome.yaml
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Discover latest version:
|
|
34
|
+
```bash
|
|
35
|
+
$ napt discover recipes/Google/chrome.yaml
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Build PSADT package:
|
|
39
|
+
```bash
|
|
40
|
+
$ napt build recipes/Google/chrome.yaml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Create .intunewin package:
|
|
44
|
+
```bash
|
|
45
|
+
$ napt package builds/napt-chrome/142.0.7444.60/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Enable verbose output:
|
|
49
|
+
```bash
|
|
50
|
+
$ napt discover recipes/Google/chrome.yaml --verbose
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Enable debug output:
|
|
54
|
+
```bash
|
|
55
|
+
$ napt discover recipes/Google/chrome.yaml --debug
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Exit Codes:
|
|
59
|
+
|
|
60
|
+
- 0: Success
|
|
61
|
+
- 1: Error (configuration, download, or validation failure)
|
|
62
|
+
|
|
63
|
+
Note:
|
|
64
|
+
The CLI uses argparse for command parsing (stdlib, zero dependencies).
|
|
65
|
+
Commands are registered with subparsers for clean organization.
|
|
66
|
+
Each command has its own handler function (cmd_<command>).
|
|
67
|
+
Verbose mode shows full tracebacks on errors for debugging.
|
|
68
|
+
Debug mode implies verbose mode and shows detailed configuration dumps.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
from __future__ import annotations
|
|
73
|
+
|
|
74
|
+
import argparse
|
|
75
|
+
from importlib.metadata import version
|
|
76
|
+
from pathlib import Path
|
|
77
|
+
import sys
|
|
78
|
+
|
|
79
|
+
from napt.build import build_package, create_intunewin
|
|
80
|
+
from napt.core import discover_recipe
|
|
81
|
+
from napt.exceptions import (
|
|
82
|
+
ConfigError,
|
|
83
|
+
NAPTError,
|
|
84
|
+
NetworkError,
|
|
85
|
+
PackagingError,
|
|
86
|
+
)
|
|
87
|
+
from napt.logging import get_logger, set_global_logger
|
|
88
|
+
from napt.validation import validate_recipe
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def cmd_validate(args: argparse.Namespace) -> int:
|
|
92
|
+
"""Handler for 'napt validate' command.
|
|
93
|
+
|
|
94
|
+
Validates recipe syntax and configuration without downloading files or
|
|
95
|
+
making network calls. This is useful for quick feedback during recipe
|
|
96
|
+
development and for CI/CD pre-checks.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
args: Parsed command-line arguments containing
|
|
100
|
+
recipe path and verbose flag.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Exit code (0 for valid recipe, 1 for invalid).
|
|
104
|
+
|
|
105
|
+
Note:
|
|
106
|
+
Prints validation results, errors, and warnings to stdout.
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
# Configure global logger
|
|
110
|
+
logger = get_logger(verbose=args.verbose, debug=args.debug)
|
|
111
|
+
set_global_logger(logger)
|
|
112
|
+
|
|
113
|
+
recipe_path = Path(args.recipe).resolve()
|
|
114
|
+
|
|
115
|
+
print(f"Validating recipe: {recipe_path}")
|
|
116
|
+
print()
|
|
117
|
+
|
|
118
|
+
# Validate the recipe
|
|
119
|
+
result = validate_recipe(recipe_path)
|
|
120
|
+
|
|
121
|
+
# Display results
|
|
122
|
+
print("=" * 70)
|
|
123
|
+
print("VALIDATION RESULTS")
|
|
124
|
+
print("=" * 70)
|
|
125
|
+
print(f"Recipe: {result.recipe_path}")
|
|
126
|
+
print(f"Status: {result.status.upper()}")
|
|
127
|
+
print(f"App Count: {result.app_count}")
|
|
128
|
+
print()
|
|
129
|
+
|
|
130
|
+
# Show warnings if any
|
|
131
|
+
if result.warnings:
|
|
132
|
+
print(f"Warnings ({len(result.warnings)}):")
|
|
133
|
+
for warning in result.warnings:
|
|
134
|
+
print(f" [WARNING] {warning}")
|
|
135
|
+
print()
|
|
136
|
+
|
|
137
|
+
# Show errors if any
|
|
138
|
+
if result.errors:
|
|
139
|
+
print(f"Errors ({len(result.errors)}):")
|
|
140
|
+
for error in result.errors:
|
|
141
|
+
print(f" [X] {error}")
|
|
142
|
+
print()
|
|
143
|
+
|
|
144
|
+
print("=" * 70)
|
|
145
|
+
|
|
146
|
+
if result.status == "valid":
|
|
147
|
+
print()
|
|
148
|
+
print("[SUCCESS] Recipe is valid!")
|
|
149
|
+
return 0
|
|
150
|
+
else:
|
|
151
|
+
print()
|
|
152
|
+
print(f"[FAILED] Recipe validation failed with {len(result.errors)} error(s).")
|
|
153
|
+
return 1
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cmd_discover(args: argparse.Namespace) -> int:
|
|
157
|
+
"""Handler for 'napt discover' command.
|
|
158
|
+
|
|
159
|
+
Discovers the latest version of an application by querying the source
|
|
160
|
+
and downloading the installer. This command validates the recipe YAML,
|
|
161
|
+
uses the configured discovery strategy to find the latest version,
|
|
162
|
+
downloads the installer (or uses cached version via ETag), extracts
|
|
163
|
+
version information, and updates the state file with caching info.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
args: Parsed command-line arguments containing
|
|
167
|
+
recipe path, output directory, state file path, and flags.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Exit code (0 for success, 1 for failure).
|
|
171
|
+
|
|
172
|
+
Note:
|
|
173
|
+
Downloads installer file to output_dir (or uses cached version).
|
|
174
|
+
Updates state file with version and ETag information. Prints progress
|
|
175
|
+
and results to stdout. Prints errors with optional traceback if verbose/debug.
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
# Configure global logger
|
|
179
|
+
logger = get_logger(verbose=args.verbose, debug=args.debug)
|
|
180
|
+
set_global_logger(logger)
|
|
181
|
+
|
|
182
|
+
recipe_path = Path(args.recipe).resolve()
|
|
183
|
+
output_dir = Path(args.output_dir).resolve()
|
|
184
|
+
|
|
185
|
+
if not recipe_path.exists():
|
|
186
|
+
print(f"Error: Recipe file not found: {recipe_path}")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
print(f"Discovering version for recipe: {recipe_path}")
|
|
190
|
+
print(f"Output directory: {output_dir}")
|
|
191
|
+
print()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
result = discover_recipe(
|
|
195
|
+
recipe_path,
|
|
196
|
+
output_dir,
|
|
197
|
+
state_file=args.state_file if not args.stateless else None,
|
|
198
|
+
stateless=args.stateless,
|
|
199
|
+
)
|
|
200
|
+
except (ConfigError, NetworkError, PackagingError) as err:
|
|
201
|
+
print(f"Error: {err}")
|
|
202
|
+
if args.verbose or args.debug:
|
|
203
|
+
import traceback
|
|
204
|
+
|
|
205
|
+
traceback.print_exc()
|
|
206
|
+
return 1
|
|
207
|
+
except NAPTError as err:
|
|
208
|
+
# Catch any other NAPT errors we might have missed
|
|
209
|
+
print(f"Error: {err}")
|
|
210
|
+
if args.verbose or args.debug:
|
|
211
|
+
import traceback
|
|
212
|
+
|
|
213
|
+
traceback.print_exc()
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
# Display results
|
|
217
|
+
print("=" * 70)
|
|
218
|
+
print("DISCOVERY RESULTS")
|
|
219
|
+
print("=" * 70)
|
|
220
|
+
print(f"App Name: {result.app_name}")
|
|
221
|
+
print(f"App ID: {result.app_id}")
|
|
222
|
+
print(f"Strategy: {result.strategy}")
|
|
223
|
+
print(f"Version: {result.version}")
|
|
224
|
+
print(f"Version Source: {result.version_source}")
|
|
225
|
+
print(f"File Path: {result.file_path}")
|
|
226
|
+
print(f"SHA-256: {result.sha256}")
|
|
227
|
+
print(f"Status: {result.status}")
|
|
228
|
+
print("=" * 70)
|
|
229
|
+
print()
|
|
230
|
+
print("[SUCCESS] Version discovered successfully!")
|
|
231
|
+
|
|
232
|
+
return 0
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def cmd_build(args: argparse.Namespace) -> int:
|
|
236
|
+
"""Handler for 'napt build' command.
|
|
237
|
+
|
|
238
|
+
Builds a PSADT package from a recipe and downloaded installer. This command
|
|
239
|
+
loads the recipe configuration, finds the downloaded installer, extracts
|
|
240
|
+
version from the installer file (filesystem is truth), downloads/caches
|
|
241
|
+
the specified PSADT release, creates build directory structure, copies
|
|
242
|
+
PSADT files pristine from cache, generates Invoke-AppDeployToolkit.ps1
|
|
243
|
+
with recipe values, copies installer to Files/ directory, and applies
|
|
244
|
+
custom branding.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
args: Parsed command-line arguments containing
|
|
248
|
+
recipe path, downloads directory, output directory, and flags.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Exit code (0 for success, 1 for failure).
|
|
252
|
+
|
|
253
|
+
Note:
|
|
254
|
+
Creates build directory structure. Downloads PSADT release if not cached.
|
|
255
|
+
Generates Invoke-AppDeployToolkit.ps1. Copies files to build directory.
|
|
256
|
+
Prints progress and results to stdout.
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
# Configure global logger
|
|
260
|
+
logger = get_logger(verbose=args.verbose, debug=args.debug)
|
|
261
|
+
set_global_logger(logger)
|
|
262
|
+
|
|
263
|
+
recipe_path = Path(args.recipe).resolve()
|
|
264
|
+
downloads_dir = Path(args.downloads_dir).resolve()
|
|
265
|
+
output_dir = Path(args.output_dir) if args.output_dir else None
|
|
266
|
+
|
|
267
|
+
if not recipe_path.exists():
|
|
268
|
+
print(f"Error: Recipe file not found: {recipe_path}")
|
|
269
|
+
return 1
|
|
270
|
+
|
|
271
|
+
if not downloads_dir.exists():
|
|
272
|
+
print(f"Error: Downloads directory not found: {downloads_dir}")
|
|
273
|
+
print("Run 'napt discover' first to download the installer.")
|
|
274
|
+
return 1
|
|
275
|
+
|
|
276
|
+
print(f"Building PSADT package for recipe: {recipe_path}")
|
|
277
|
+
print(f"Downloads directory: {downloads_dir}")
|
|
278
|
+
if output_dir:
|
|
279
|
+
print(f"Output directory: {output_dir}")
|
|
280
|
+
print()
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
result = build_package(
|
|
284
|
+
recipe_path,
|
|
285
|
+
downloads_dir=downloads_dir,
|
|
286
|
+
output_dir=output_dir,
|
|
287
|
+
)
|
|
288
|
+
except (ConfigError, NetworkError, PackagingError) as err:
|
|
289
|
+
print(f"Error: {err}")
|
|
290
|
+
if args.verbose or args.debug:
|
|
291
|
+
import traceback
|
|
292
|
+
|
|
293
|
+
traceback.print_exc()
|
|
294
|
+
return 1
|
|
295
|
+
except NAPTError as err:
|
|
296
|
+
# Catch any other NAPT errors we might have missed
|
|
297
|
+
print(f"Error: {err}")
|
|
298
|
+
if args.verbose or args.debug:
|
|
299
|
+
import traceback
|
|
300
|
+
|
|
301
|
+
traceback.print_exc()
|
|
302
|
+
return 1
|
|
303
|
+
|
|
304
|
+
# Display results
|
|
305
|
+
print("=" * 70)
|
|
306
|
+
print("BUILD RESULTS")
|
|
307
|
+
print("=" * 70)
|
|
308
|
+
print(f"App Name: {result.app_name}")
|
|
309
|
+
print(f"App ID: {result.app_id}")
|
|
310
|
+
print(f"Version: {result.version}")
|
|
311
|
+
print(f"PSADT Version: {result.psadt_version}")
|
|
312
|
+
print(f"Build Directory: {result.build_dir}")
|
|
313
|
+
print(f"Status: {result.status}")
|
|
314
|
+
print("=" * 70)
|
|
315
|
+
print()
|
|
316
|
+
print("[SUCCESS] PSADT package built successfully!")
|
|
317
|
+
|
|
318
|
+
return 0
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def cmd_package(args: argparse.Namespace) -> int:
|
|
322
|
+
"""Handler for 'napt package' command.
|
|
323
|
+
|
|
324
|
+
Creates a .intunewin package from a built PSADT directory. This command
|
|
325
|
+
verifies the build directory has valid PSADT structure, downloads/caches
|
|
326
|
+
IntuneWinAppUtil.exe if needed, runs IntuneWinAppUtil.exe to create
|
|
327
|
+
.intunewin package, and optionally cleans the source build directory
|
|
328
|
+
after packaging.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
args: Parsed command-line arguments containing
|
|
332
|
+
build directory path, output directory, clean flag, and debug flags.
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
Exit code (0 for success, 1 for failure).
|
|
336
|
+
|
|
337
|
+
Note:
|
|
338
|
+
Creates .intunewin file in output directory. Downloads IntuneWinAppUtil.exe
|
|
339
|
+
if not cached. Optionally removes build directory if --clean-source.
|
|
340
|
+
Prints progress and results to stdout.
|
|
341
|
+
|
|
342
|
+
"""
|
|
343
|
+
# Configure global logger
|
|
344
|
+
logger = get_logger(verbose=args.verbose, debug=args.debug)
|
|
345
|
+
set_global_logger(logger)
|
|
346
|
+
|
|
347
|
+
build_dir = Path(args.build_dir).resolve()
|
|
348
|
+
output_dir = Path(args.output_dir) if args.output_dir else None
|
|
349
|
+
|
|
350
|
+
if not build_dir.exists():
|
|
351
|
+
print(f"Error: Build directory not found: {build_dir}")
|
|
352
|
+
return 1
|
|
353
|
+
|
|
354
|
+
print(f"Creating .intunewin package from: {build_dir}")
|
|
355
|
+
if output_dir:
|
|
356
|
+
print(f"Output directory: {output_dir}")
|
|
357
|
+
print()
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
result = create_intunewin(
|
|
361
|
+
build_dir,
|
|
362
|
+
output_dir=output_dir,
|
|
363
|
+
clean_source=args.clean_source,
|
|
364
|
+
)
|
|
365
|
+
except (ConfigError, NetworkError, PackagingError) as err:
|
|
366
|
+
print(f"Error: {err}")
|
|
367
|
+
if args.verbose or args.debug:
|
|
368
|
+
import traceback
|
|
369
|
+
|
|
370
|
+
traceback.print_exc()
|
|
371
|
+
return 1
|
|
372
|
+
except NAPTError as err:
|
|
373
|
+
# Catch any other NAPT errors we might have missed
|
|
374
|
+
print(f"Error: {err}")
|
|
375
|
+
if args.verbose or args.debug:
|
|
376
|
+
import traceback
|
|
377
|
+
|
|
378
|
+
traceback.print_exc()
|
|
379
|
+
return 1
|
|
380
|
+
|
|
381
|
+
# Display results
|
|
382
|
+
print("=" * 70)
|
|
383
|
+
print("PACKAGE RESULTS")
|
|
384
|
+
print("=" * 70)
|
|
385
|
+
print(f"App ID: {result.app_id}")
|
|
386
|
+
print(f"Version: {result.version}")
|
|
387
|
+
print(f"Package Path: {result.package_path}")
|
|
388
|
+
if args.clean_source:
|
|
389
|
+
print(f"Build Directory: {result.build_dir} (removed)")
|
|
390
|
+
else:
|
|
391
|
+
print(f"Build Directory: {result.build_dir}")
|
|
392
|
+
print(f"Status: {result.status}")
|
|
393
|
+
print("=" * 70)
|
|
394
|
+
print()
|
|
395
|
+
print("[SUCCESS] .intunewin package created successfully!")
|
|
396
|
+
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def main() -> None:
|
|
401
|
+
"""Main entry point for the napt CLI.
|
|
402
|
+
|
|
403
|
+
This function is registered as the 'napt' console script in pyproject.toml.
|
|
404
|
+
"""
|
|
405
|
+
parser = argparse.ArgumentParser(
|
|
406
|
+
prog="napt",
|
|
407
|
+
description="NAPT - Not a Pkg Tool for Windows/Intune packaging with PSADT",
|
|
408
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
parser.add_argument(
|
|
412
|
+
"--version",
|
|
413
|
+
action="version",
|
|
414
|
+
version=f"napt {version('napt')}",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
subparsers = parser.add_subparsers(
|
|
418
|
+
dest="command",
|
|
419
|
+
help="Available commands",
|
|
420
|
+
required=True,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# 'validate' command
|
|
424
|
+
parser_validate = subparsers.add_parser(
|
|
425
|
+
"validate",
|
|
426
|
+
help="Validate recipe syntax and configuration (no downloads)",
|
|
427
|
+
description=(
|
|
428
|
+
"Check recipe YAML for syntax errors and configuration issues "
|
|
429
|
+
"without making network calls.\n\n"
|
|
430
|
+
"Examples:\n"
|
|
431
|
+
" napt validate recipes/Google/chrome.yaml\n"
|
|
432
|
+
" napt validate recipes/Google/chrome.yaml --verbose\n\n"
|
|
433
|
+
"See docs for more examples and workflows."
|
|
434
|
+
),
|
|
435
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
436
|
+
)
|
|
437
|
+
parser_validate.add_argument(
|
|
438
|
+
"recipe",
|
|
439
|
+
help="Path to the recipe YAML file",
|
|
440
|
+
)
|
|
441
|
+
parser_validate.add_argument(
|
|
442
|
+
"-v",
|
|
443
|
+
"--verbose",
|
|
444
|
+
action="store_true",
|
|
445
|
+
help="Show validation progress and details",
|
|
446
|
+
)
|
|
447
|
+
parser_validate.add_argument(
|
|
448
|
+
"-d",
|
|
449
|
+
"--debug",
|
|
450
|
+
action="store_true",
|
|
451
|
+
help="Show detailed debugging output (implies --verbose)",
|
|
452
|
+
)
|
|
453
|
+
parser_validate.set_defaults(func=cmd_validate)
|
|
454
|
+
|
|
455
|
+
# 'discover' command
|
|
456
|
+
parser_discover = subparsers.add_parser(
|
|
457
|
+
"discover",
|
|
458
|
+
help="Discover latest version and download installer",
|
|
459
|
+
description=(
|
|
460
|
+
"Find the latest version using the configured discovery strategy "
|
|
461
|
+
"and download the installer.\n\n"
|
|
462
|
+
"Examples:\n"
|
|
463
|
+
" napt discover recipes/Google/chrome.yaml\n"
|
|
464
|
+
" napt discover recipes/Google/chrome.yaml --verbose\n"
|
|
465
|
+
" napt discover recipes/Google/chrome.yaml --stateless\n\n"
|
|
466
|
+
"See docs for more examples and workflows."
|
|
467
|
+
),
|
|
468
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
469
|
+
)
|
|
470
|
+
parser_discover.add_argument(
|
|
471
|
+
"recipe",
|
|
472
|
+
help="Path to the recipe YAML file",
|
|
473
|
+
)
|
|
474
|
+
parser_discover.add_argument(
|
|
475
|
+
"--output-dir",
|
|
476
|
+
default="./downloads",
|
|
477
|
+
help="Directory to save downloaded files (default: ./downloads)",
|
|
478
|
+
)
|
|
479
|
+
parser_discover.add_argument(
|
|
480
|
+
"--state-file",
|
|
481
|
+
type=Path,
|
|
482
|
+
default=Path("state/versions.json"),
|
|
483
|
+
help=(
|
|
484
|
+
"State file for version tracking and ETag caching "
|
|
485
|
+
"(default: state/versions.json)"
|
|
486
|
+
),
|
|
487
|
+
)
|
|
488
|
+
parser_discover.add_argument(
|
|
489
|
+
"--stateless",
|
|
490
|
+
action="store_true",
|
|
491
|
+
help="Disable state tracking (no caching, always download full files)",
|
|
492
|
+
)
|
|
493
|
+
parser_discover.add_argument(
|
|
494
|
+
"-v",
|
|
495
|
+
"--verbose",
|
|
496
|
+
action="store_true",
|
|
497
|
+
help="Show progress and high-level status updates",
|
|
498
|
+
)
|
|
499
|
+
parser_discover.add_argument(
|
|
500
|
+
"-d",
|
|
501
|
+
"--debug",
|
|
502
|
+
action="store_true",
|
|
503
|
+
help="Show detailed debugging output (implies --verbose)",
|
|
504
|
+
)
|
|
505
|
+
parser_discover.set_defaults(func=cmd_discover)
|
|
506
|
+
|
|
507
|
+
# 'build' command
|
|
508
|
+
parser_build = subparsers.add_parser(
|
|
509
|
+
"build",
|
|
510
|
+
help="Build PSADT package from recipe and installer",
|
|
511
|
+
description=(
|
|
512
|
+
"Create a PSADT deployment package from a recipe and "
|
|
513
|
+
"downloaded installer.\n\n"
|
|
514
|
+
"Examples:\n"
|
|
515
|
+
" napt build recipes/Google/chrome.yaml\n"
|
|
516
|
+
" napt build recipes/Google/chrome.yaml --verbose\n"
|
|
517
|
+
" napt build recipes/Google/chrome.yaml --output-dir ./builds\n\n"
|
|
518
|
+
"See docs for more examples and workflows."
|
|
519
|
+
),
|
|
520
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
521
|
+
)
|
|
522
|
+
parser_build.add_argument(
|
|
523
|
+
"recipe",
|
|
524
|
+
help="Path to the recipe YAML file",
|
|
525
|
+
)
|
|
526
|
+
parser_build.add_argument(
|
|
527
|
+
"--downloads-dir",
|
|
528
|
+
default="./downloads",
|
|
529
|
+
help="Directory containing the downloaded installer (default: ./downloads)",
|
|
530
|
+
)
|
|
531
|
+
parser_build.add_argument(
|
|
532
|
+
"--output-dir",
|
|
533
|
+
default=None,
|
|
534
|
+
help="Base directory for build output (default: from config or ./builds)",
|
|
535
|
+
)
|
|
536
|
+
parser_build.add_argument(
|
|
537
|
+
"-v",
|
|
538
|
+
"--verbose",
|
|
539
|
+
action="store_true",
|
|
540
|
+
help="Show progress and high-level status updates",
|
|
541
|
+
)
|
|
542
|
+
parser_build.add_argument(
|
|
543
|
+
"-d",
|
|
544
|
+
"--debug",
|
|
545
|
+
action="store_true",
|
|
546
|
+
help="Show detailed debugging output (implies --verbose)",
|
|
547
|
+
)
|
|
548
|
+
parser_build.set_defaults(func=cmd_build)
|
|
549
|
+
|
|
550
|
+
# 'package' command
|
|
551
|
+
parser_package = subparsers.add_parser(
|
|
552
|
+
"package",
|
|
553
|
+
help="Create .intunewin package from PSADT build directory",
|
|
554
|
+
description=(
|
|
555
|
+
"Package a built PSADT directory into a .intunewin file "
|
|
556
|
+
"for Intune deployment.\n\n"
|
|
557
|
+
"Examples:\n"
|
|
558
|
+
" napt package builds/napt-chrome/142.0.7444.60/\n"
|
|
559
|
+
" napt package builds/napt-chrome/142.0.7444.60/ --clean-source\n"
|
|
560
|
+
" napt package builds/napt-chrome/142.0.7444.60/ --verbose\n\n"
|
|
561
|
+
"See docs for more examples and workflows."
|
|
562
|
+
),
|
|
563
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
564
|
+
)
|
|
565
|
+
parser_package.add_argument(
|
|
566
|
+
"build_dir",
|
|
567
|
+
help="Path to the built PSADT package directory",
|
|
568
|
+
)
|
|
569
|
+
parser_package.add_argument(
|
|
570
|
+
"--output-dir",
|
|
571
|
+
default=None,
|
|
572
|
+
help="Directory for .intunewin output (default: packages/{app_id}/)",
|
|
573
|
+
)
|
|
574
|
+
parser_package.add_argument(
|
|
575
|
+
"--clean-source",
|
|
576
|
+
action="store_true",
|
|
577
|
+
help="Remove the build directory after packaging",
|
|
578
|
+
)
|
|
579
|
+
parser_package.add_argument(
|
|
580
|
+
"-v",
|
|
581
|
+
"--verbose",
|
|
582
|
+
action="store_true",
|
|
583
|
+
help="Show progress and high-level status updates",
|
|
584
|
+
)
|
|
585
|
+
parser_package.add_argument(
|
|
586
|
+
"-d",
|
|
587
|
+
"--debug",
|
|
588
|
+
action="store_true",
|
|
589
|
+
help="Show detailed debugging output (implies --verbose)",
|
|
590
|
+
)
|
|
591
|
+
parser_package.set_defaults(func=cmd_package)
|
|
592
|
+
|
|
593
|
+
# Parse and dispatch
|
|
594
|
+
args = parser.parse_args()
|
|
595
|
+
|
|
596
|
+
# Call the appropriate command handler
|
|
597
|
+
exit_code = args.func(args)
|
|
598
|
+
sys.exit(exit_code)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
if __name__ == "__main__":
|
|
602
|
+
main()
|
napt/config/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright 2025 Roger Cibrian
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Configuration loading and management for NAPT.
|
|
16
|
+
|
|
17
|
+
This module provides tools for loading, merging, and validating YAML-based
|
|
18
|
+
configuration files with a layered approach:
|
|
19
|
+
|
|
20
|
+
- Organization-wide defaults (defaults/org.yaml)
|
|
21
|
+
- Vendor-specific defaults (defaults/vendors/<Vendor>.yaml)
|
|
22
|
+
- Recipe-specific configuration (recipes/<Vendor>/<app>.yaml)
|
|
23
|
+
|
|
24
|
+
The loader performs deep merging where dicts are merged recursively and
|
|
25
|
+
lists/scalars are replaced (last wins). Relative paths are resolved against
|
|
26
|
+
the recipe file location for relocatability.
|
|
27
|
+
|
|
28
|
+
Example:
|
|
29
|
+
Basic usage:
|
|
30
|
+
```python
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from napt.config import load_effective_config
|
|
33
|
+
|
|
34
|
+
config = load_effective_config(Path("recipes/Google/chrome.yaml"))
|
|
35
|
+
app = config.get("app")
|
|
36
|
+
print(app["name"]) # "Google Chrome"
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from .loader import load_effective_config
|
|
41
|
+
|
|
42
|
+
__all__ = ["load_effective_config"]
|