cmem-cmemc 25.5.0rc1__py3-none-any.whl → 26.1.0rc1__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.
- cmem_cmemc/cli.py +11 -6
- cmem_cmemc/command.py +1 -1
- cmem_cmemc/command_group.py +59 -31
- cmem_cmemc/commands/acl.py +403 -26
- cmem_cmemc/commands/admin.py +10 -10
- cmem_cmemc/commands/client.py +12 -5
- cmem_cmemc/commands/config.py +106 -12
- cmem_cmemc/commands/dataset.py +163 -172
- cmem_cmemc/commands/file.py +509 -0
- cmem_cmemc/commands/graph.py +200 -72
- cmem_cmemc/commands/graph_imports.py +12 -5
- cmem_cmemc/commands/graph_insights.py +157 -53
- cmem_cmemc/commands/metrics.py +15 -9
- cmem_cmemc/commands/migration.py +12 -4
- cmem_cmemc/commands/package.py +548 -0
- cmem_cmemc/commands/project.py +157 -22
- cmem_cmemc/commands/python.py +9 -5
- cmem_cmemc/commands/query.py +119 -25
- cmem_cmemc/commands/scheduler.py +6 -4
- cmem_cmemc/commands/store.py +2 -1
- cmem_cmemc/commands/user.py +124 -24
- cmem_cmemc/commands/validation.py +15 -10
- cmem_cmemc/commands/variable.py +264 -61
- cmem_cmemc/commands/vocabulary.py +31 -17
- cmem_cmemc/commands/workflow.py +21 -11
- cmem_cmemc/completion.py +126 -109
- cmem_cmemc/context.py +40 -10
- cmem_cmemc/exceptions.py +8 -2
- cmem_cmemc/manual_helper/graph.py +2 -2
- cmem_cmemc/manual_helper/multi_page.py +5 -7
- cmem_cmemc/object_list.py +234 -7
- cmem_cmemc/placeholder.py +2 -2
- cmem_cmemc/string_processor.py +153 -4
- cmem_cmemc/title_helper.py +50 -0
- cmem_cmemc/utils.py +9 -8
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/METADATA +7 -6
- cmem_cmemc-26.1.0rc1.dist-info/RECORD +62 -0
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/WHEEL +1 -1
- cmem_cmemc/commands/resource.py +0 -220
- cmem_cmemc-25.5.0rc1.dist-info/RECORD +0 -61
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/entry_points.txt +0 -0
- {cmem_cmemc-25.5.0rc1.dist-info → cmem_cmemc-26.1.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
"""Package command group"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import requests
|
|
9
|
+
from click.shell_completion import CompletionItem
|
|
10
|
+
from cmem_client.client import Client
|
|
11
|
+
from cmem_client.repositories.marketplace_packages import (
|
|
12
|
+
MarketplacePackagesExportConfig,
|
|
13
|
+
MarketplacePackagesImportConfig,
|
|
14
|
+
MarketplacePackagesRepository,
|
|
15
|
+
)
|
|
16
|
+
from eccenca_marketplace_client.manifests import AbstractPackageManifest, ManifestMetadata
|
|
17
|
+
from eccenca_marketplace_client.package_version import PackageVersion
|
|
18
|
+
from pydantic_extra_types.semantic_version import SemanticVersion
|
|
19
|
+
|
|
20
|
+
from cmem_cmemc import completion
|
|
21
|
+
from cmem_cmemc.command import CmemcCommand
|
|
22
|
+
from cmem_cmemc.command_group import CmemcGroup
|
|
23
|
+
from cmem_cmemc.completion import suppress_completion_errors
|
|
24
|
+
from cmem_cmemc.context import ApplicationContext
|
|
25
|
+
from cmem_cmemc.exceptions import CmemcError
|
|
26
|
+
from cmem_cmemc.object_list import (
|
|
27
|
+
DirectValuePropertyFilter,
|
|
28
|
+
ObjectList,
|
|
29
|
+
compare_regex,
|
|
30
|
+
compare_str_equality,
|
|
31
|
+
)
|
|
32
|
+
from cmem_cmemc.parameter_types.path import ClickSmartPath
|
|
33
|
+
from cmem_cmemc.utils import struct_to_table
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_installed_packages_list(ctx: click.Context) -> list[dict]:
|
|
37
|
+
"""Get the list of installed packages"""
|
|
38
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
39
|
+
client = Client.from_cmempy()
|
|
40
|
+
packages: MarketplacePackagesRepository = client.marketplace_packages
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
"id": package_.package_version.manifest.package_id,
|
|
44
|
+
"type": str(package_.package_version.manifest.package_type).replace(
|
|
45
|
+
"PackageTypes.", ""
|
|
46
|
+
),
|
|
47
|
+
"version": str(package_.package_version.manifest.package_version),
|
|
48
|
+
"name": package_.package_version.manifest.metadata.name,
|
|
49
|
+
"description": package_.package_version.manifest.metadata.description,
|
|
50
|
+
}
|
|
51
|
+
for package_ in packages.values()
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
installed_packages_list = ObjectList(
|
|
56
|
+
name="installed packages",
|
|
57
|
+
get_objects=get_installed_packages_list,
|
|
58
|
+
filters=[
|
|
59
|
+
DirectValuePropertyFilter(
|
|
60
|
+
name="type",
|
|
61
|
+
description="Filter list by package type.",
|
|
62
|
+
property_key="type",
|
|
63
|
+
),
|
|
64
|
+
DirectValuePropertyFilter(
|
|
65
|
+
name="name",
|
|
66
|
+
description="Filter list by regex matching the package name.",
|
|
67
|
+
property_key="name",
|
|
68
|
+
compare=compare_regex,
|
|
69
|
+
fixed_completion=[],
|
|
70
|
+
),
|
|
71
|
+
DirectValuePropertyFilter(
|
|
72
|
+
name="id",
|
|
73
|
+
description="Filter list by package ID.",
|
|
74
|
+
property_key="id",
|
|
75
|
+
compare=compare_str_equality,
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@suppress_completion_errors
|
|
82
|
+
def _complete_installed_package_ids(
|
|
83
|
+
ctx: click.Context,
|
|
84
|
+
param: click.Argument, # noqa: ARG001
|
|
85
|
+
incomplete: str,
|
|
86
|
+
) -> list[CompletionItem]:
|
|
87
|
+
"""Prepare a list of IDs of installed packages."""
|
|
88
|
+
ApplicationContext.set_connection_from_params(ctx.find_root().params)
|
|
89
|
+
candidates = [
|
|
90
|
+
(_["id"], f"{_['version']}: {_['name']}")
|
|
91
|
+
for _ in installed_packages_list.apply_filters(ctx=ctx)
|
|
92
|
+
]
|
|
93
|
+
return completion.finalize_completion(candidates=candidates, incomplete=incomplete)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@click.command(cls=CmemcCommand, name="create")
|
|
97
|
+
@click.argument("package_id", required=True)
|
|
98
|
+
@click.option(
|
|
99
|
+
"--name",
|
|
100
|
+
type=click.STRING,
|
|
101
|
+
help=ManifestMetadata.model_fields.get("name").description + " Defaults to package ID.",
|
|
102
|
+
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--version",
|
|
105
|
+
type=click.STRING,
|
|
106
|
+
default="0.0.1",
|
|
107
|
+
show_default=True,
|
|
108
|
+
help=AbstractPackageManifest.model_fields.get("package_version").description,
|
|
109
|
+
)
|
|
110
|
+
@click.option(
|
|
111
|
+
"--description",
|
|
112
|
+
type=click.STRING,
|
|
113
|
+
help=ManifestMetadata.model_fields.get("description").description,
|
|
114
|
+
default="This is the first version of a wonderful eccenca Corporate Memory package 🤓",
|
|
115
|
+
show_default=True,
|
|
116
|
+
)
|
|
117
|
+
@click.pass_obj
|
|
118
|
+
def create_command(
|
|
119
|
+
app: ApplicationContext, package_id: str, version: str, name: str | None, description: str
|
|
120
|
+
) -> None:
|
|
121
|
+
"""Initialize an empty package directory with a minimal manifest."""
|
|
122
|
+
if Path(package_id).exists():
|
|
123
|
+
raise click.UsageError(f"Package directory '{package_id}' already exists.")
|
|
124
|
+
if name is None:
|
|
125
|
+
name = package_id
|
|
126
|
+
manifest_src = {
|
|
127
|
+
"package_id": package_id,
|
|
128
|
+
"package_type": "project",
|
|
129
|
+
"package_version": version,
|
|
130
|
+
"metadata": {
|
|
131
|
+
"name": name,
|
|
132
|
+
"description": description,
|
|
133
|
+
},
|
|
134
|
+
"files": [],
|
|
135
|
+
}
|
|
136
|
+
package_version = PackageVersion.from_json(json.dumps(manifest_src))
|
|
137
|
+
directory = Path(package_id)
|
|
138
|
+
app.echo_info(f"Initialize package directory '{directory}' ... ", nl=False)
|
|
139
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
manifest = directory / "manifest.json"
|
|
141
|
+
manifest.write_text(package_version.manifest.model_dump_json(indent=2))
|
|
142
|
+
app.echo_success("done")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@click.command(cls=CmemcCommand, name="inspect")
|
|
146
|
+
@click.argument(
|
|
147
|
+
"PACKAGE_PATH",
|
|
148
|
+
required=True,
|
|
149
|
+
type=ClickSmartPath(
|
|
150
|
+
allow_dash=False,
|
|
151
|
+
dir_okay=True,
|
|
152
|
+
readable=True,
|
|
153
|
+
exists=True,
|
|
154
|
+
remote_okay=True,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
@click.option(
|
|
158
|
+
"--key",
|
|
159
|
+
"key",
|
|
160
|
+
help="Get a specific key only from the manifest.",
|
|
161
|
+
)
|
|
162
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
163
|
+
@click.pass_obj
|
|
164
|
+
def inspect_command(app: ApplicationContext, package_path: Path, key: str, raw: str) -> None:
|
|
165
|
+
"""Inspect the manifest of a package."""
|
|
166
|
+
path = Path(package_path)
|
|
167
|
+
package_version = (
|
|
168
|
+
PackageVersion.from_directory(path) if path.is_dir() else PackageVersion.from_archive(path)
|
|
169
|
+
)
|
|
170
|
+
manifest = package_version.manifest
|
|
171
|
+
manifest_data = json.loads(manifest.model_dump_json(indent=2))
|
|
172
|
+
if raw:
|
|
173
|
+
app.echo_info_json(manifest_data)
|
|
174
|
+
return
|
|
175
|
+
if key:
|
|
176
|
+
table = [
|
|
177
|
+
line
|
|
178
|
+
for line in struct_to_table(manifest_data)
|
|
179
|
+
if line[0].startswith(key) or key == "all"
|
|
180
|
+
]
|
|
181
|
+
if len(table) == 1:
|
|
182
|
+
app.echo_info(table[0][1])
|
|
183
|
+
return
|
|
184
|
+
if len(table) == 0:
|
|
185
|
+
raise CmemcError(f"No values for key '{key}'.")
|
|
186
|
+
app.echo_info_table(table, headers=["Key", "Value"], sort_column=0)
|
|
187
|
+
return
|
|
188
|
+
table = struct_to_table(manifest_data)
|
|
189
|
+
app.echo_info_table(
|
|
190
|
+
table,
|
|
191
|
+
headers=["Key", "Value"],
|
|
192
|
+
sort_column=0,
|
|
193
|
+
caption=f"Manifest of package '{manifest.package_id}' in"
|
|
194
|
+
f" {path.name} (v{manifest.package_version})",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@click.command(cls=CmemcCommand, name="list")
|
|
199
|
+
@click.option(
|
|
200
|
+
"--filter",
|
|
201
|
+
"filter_",
|
|
202
|
+
multiple=True,
|
|
203
|
+
type=(str, str),
|
|
204
|
+
shell_complete=installed_packages_list.complete_values,
|
|
205
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
206
|
+
)
|
|
207
|
+
@click.option(
|
|
208
|
+
"--id-only",
|
|
209
|
+
is_flag=True,
|
|
210
|
+
help="Lists only package IDs. This is useful for piping the IDs into other commands.",
|
|
211
|
+
)
|
|
212
|
+
@click.option("--raw", is_flag=True, help="Outputs raw JSON.")
|
|
213
|
+
@click.pass_context
|
|
214
|
+
def list_command(
|
|
215
|
+
ctx: click.Context, filter_: tuple[tuple[str, str]], id_only: bool, raw: bool
|
|
216
|
+
) -> None:
|
|
217
|
+
"""List installed packages."""
|
|
218
|
+
app: ApplicationContext = ctx.obj
|
|
219
|
+
data = installed_packages_list.apply_filters(ctx=ctx, filter_=filter_)
|
|
220
|
+
if id_only:
|
|
221
|
+
for _ in sorted(data, key=lambda _: _["id"]):
|
|
222
|
+
app.echo_info(_["id"])
|
|
223
|
+
return
|
|
224
|
+
if raw:
|
|
225
|
+
app.echo_info_json(data)
|
|
226
|
+
return
|
|
227
|
+
table = [
|
|
228
|
+
(
|
|
229
|
+
_["id"],
|
|
230
|
+
_["version"],
|
|
231
|
+
_["type"],
|
|
232
|
+
_["name"],
|
|
233
|
+
)
|
|
234
|
+
for _ in data
|
|
235
|
+
]
|
|
236
|
+
app.echo_info_table(
|
|
237
|
+
table,
|
|
238
|
+
headers=["ID", "Version", "Type", "Name"],
|
|
239
|
+
sort_column=0,
|
|
240
|
+
empty_table_message="No installed packages found. "
|
|
241
|
+
"You can use the `package install` command to install packages.",
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@click.command(cls=CmemcCommand, name="install")
|
|
246
|
+
@click.argument(
|
|
247
|
+
"PACKAGE_ID",
|
|
248
|
+
required=False,
|
|
249
|
+
type=click.STRING,
|
|
250
|
+
)
|
|
251
|
+
@click.option(
|
|
252
|
+
"--input",
|
|
253
|
+
"-i",
|
|
254
|
+
"input_path",
|
|
255
|
+
type=ClickSmartPath(allow_dash=False, dir_okay=True, file_okay=True, readable=True),
|
|
256
|
+
help="Install a package from a package archive (.cpa) or directory.",
|
|
257
|
+
)
|
|
258
|
+
@click.option(
|
|
259
|
+
"--replace", is_flag=True, help="Replace (overwrite) existing package version, if present."
|
|
260
|
+
)
|
|
261
|
+
@click.pass_obj
|
|
262
|
+
def install_command(
|
|
263
|
+
app: ApplicationContext, package_id: str, replace: bool, input_path: str
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Install packages.
|
|
266
|
+
|
|
267
|
+
This command installs a package either from the marketplace or from local package
|
|
268
|
+
archives (.cpa) or directories.
|
|
269
|
+
"""
|
|
270
|
+
if not package_id and not input_path:
|
|
271
|
+
raise CmemcError(
|
|
272
|
+
"Nothing to install. Either specify a package ID from the marketplace, "
|
|
273
|
+
"or use the `--input` option to install a local package."
|
|
274
|
+
)
|
|
275
|
+
if package_id and input_path:
|
|
276
|
+
raise CmemcError(
|
|
277
|
+
"You can not install from the marketplace and local files at the same time."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
packages = app.client.marketplace_packages
|
|
281
|
+
if input_path:
|
|
282
|
+
package_path = Path(input_path)
|
|
283
|
+
package_version = (
|
|
284
|
+
PackageVersion.from_directory(package_path)
|
|
285
|
+
if package_path.is_dir()
|
|
286
|
+
else PackageVersion.from_archive(package_path)
|
|
287
|
+
)
|
|
288
|
+
package_id = package_version.manifest.package_id
|
|
289
|
+
app.echo_info(f"Installing package '{package_id}' from '{input_path}' ... ", nl=False)
|
|
290
|
+
packages.import_item(
|
|
291
|
+
path=package_path,
|
|
292
|
+
replace=replace,
|
|
293
|
+
configuration=MarketplacePackagesImportConfig(install_from_marketplace=False),
|
|
294
|
+
)
|
|
295
|
+
else:
|
|
296
|
+
app.echo_info(f"Installing package '{package_id}' from marketplace ... ", nl=False)
|
|
297
|
+
packages.import_item(key=package_id, replace=replace)
|
|
298
|
+
app.echo_success("done")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _filter_installed_packages(
|
|
302
|
+
ctx: click.Context, package_id: str | None, filter_: tuple[tuple[str, str]], all_: bool
|
|
303
|
+
) -> list[dict]:
|
|
304
|
+
"""Filter installed packages."""
|
|
305
|
+
if package_id is None and not filter_ and not all_:
|
|
306
|
+
raise click.UsageError("Either provide a package ID or a filter, or use the --all flag.")
|
|
307
|
+
|
|
308
|
+
if all_:
|
|
309
|
+
packages_to_work_on = installed_packages_list.apply_filters(ctx=ctx)
|
|
310
|
+
else:
|
|
311
|
+
filter_to_apply = list(filter_) if filter_ else []
|
|
312
|
+
if package_id:
|
|
313
|
+
filter_to_apply.append(("id", package_id))
|
|
314
|
+
packages_to_work_on = installed_packages_list.apply_filters(
|
|
315
|
+
ctx=ctx, filter_=filter_to_apply
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if not packages_to_work_on and package_id:
|
|
319
|
+
raise CmemcError(f"Package '{package_id}' is not installed.")
|
|
320
|
+
return packages_to_work_on
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@click.command(cls=CmemcCommand, name="uninstall")
|
|
324
|
+
@click.argument(
|
|
325
|
+
"PACKAGE_ID",
|
|
326
|
+
type=click.STRING,
|
|
327
|
+
shell_complete=_complete_installed_package_ids,
|
|
328
|
+
required=False,
|
|
329
|
+
)
|
|
330
|
+
@click.option(
|
|
331
|
+
"--filter",
|
|
332
|
+
"filter_",
|
|
333
|
+
multiple=True,
|
|
334
|
+
type=(str, str),
|
|
335
|
+
shell_complete=installed_packages_list.complete_values,
|
|
336
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
337
|
+
)
|
|
338
|
+
@click.option(
|
|
339
|
+
"-a",
|
|
340
|
+
"--all",
|
|
341
|
+
"all_",
|
|
342
|
+
is_flag=True,
|
|
343
|
+
help="Uninstall all packages. This is a dangerous option, so use it with care.",
|
|
344
|
+
)
|
|
345
|
+
@click.pass_context
|
|
346
|
+
def uninstall_command(
|
|
347
|
+
ctx: click.Context,
|
|
348
|
+
package_id: str | None,
|
|
349
|
+
filter_: tuple[tuple[str, str]],
|
|
350
|
+
all_: bool,
|
|
351
|
+
) -> None:
|
|
352
|
+
"""Uninstall installed packages."""
|
|
353
|
+
app: ApplicationContext = ctx.obj
|
|
354
|
+
packages_to_uninstall = _filter_installed_packages(
|
|
355
|
+
ctx=ctx, package_id=package_id, filter_=filter_, all_=all_
|
|
356
|
+
)
|
|
357
|
+
packages = app.client.marketplace_packages
|
|
358
|
+
for _ in packages_to_uninstall:
|
|
359
|
+
id_to_uninstall = _["id"]
|
|
360
|
+
app.echo_info(f"Uninstalling package {id_to_uninstall} ... ", nl=False)
|
|
361
|
+
packages.delete_item(id_to_uninstall)
|
|
362
|
+
app.echo_success("done")
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@click.command(cls=CmemcCommand, name="export")
|
|
366
|
+
@click.argument(
|
|
367
|
+
"PACKAGE_ID",
|
|
368
|
+
type=click.STRING,
|
|
369
|
+
shell_complete=_complete_installed_package_ids,
|
|
370
|
+
required=False,
|
|
371
|
+
)
|
|
372
|
+
@click.option(
|
|
373
|
+
"--filter",
|
|
374
|
+
"filter_",
|
|
375
|
+
multiple=True,
|
|
376
|
+
type=(str, str),
|
|
377
|
+
shell_complete=installed_packages_list.complete_values,
|
|
378
|
+
help=installed_packages_list.get_filter_help_text(),
|
|
379
|
+
)
|
|
380
|
+
@click.option(
|
|
381
|
+
"-a",
|
|
382
|
+
"--all",
|
|
383
|
+
"all_",
|
|
384
|
+
is_flag=True,
|
|
385
|
+
help="Export all installed packages.",
|
|
386
|
+
)
|
|
387
|
+
@click.option("--replace", is_flag=True, help="Replace (overwrite) existing files, if present.")
|
|
388
|
+
@click.pass_context
|
|
389
|
+
def export_command(
|
|
390
|
+
ctx: click.Context,
|
|
391
|
+
package_id: str | None,
|
|
392
|
+
filter_: tuple[tuple[str, str]],
|
|
393
|
+
all_: bool,
|
|
394
|
+
replace: bool,
|
|
395
|
+
) -> None:
|
|
396
|
+
"""Export installed packages to package directories."""
|
|
397
|
+
app: ApplicationContext = ctx.obj
|
|
398
|
+
packages_to_export = _filter_installed_packages(
|
|
399
|
+
ctx=ctx, package_id=package_id, filter_=filter_, all_=all_
|
|
400
|
+
)
|
|
401
|
+
packages = app.client.marketplace_packages
|
|
402
|
+
for _ in packages_to_export:
|
|
403
|
+
id_to_export = _["id"]
|
|
404
|
+
app.echo_info(f"Exporting package `{id_to_export}` ... ", nl=False)
|
|
405
|
+
configuration = MarketplacePackagesExportConfig(export_as_zip=False)
|
|
406
|
+
packages.export_item(
|
|
407
|
+
id_to_export,
|
|
408
|
+
path=Path(id_to_export),
|
|
409
|
+
replace=replace,
|
|
410
|
+
configuration=configuration,
|
|
411
|
+
)
|
|
412
|
+
app.echo_success("done")
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@click.command(cls=CmemcCommand, name="build")
|
|
416
|
+
@click.argument(
|
|
417
|
+
"PACKAGE_DIRECTORY",
|
|
418
|
+
required=True,
|
|
419
|
+
type=ClickSmartPath(
|
|
420
|
+
allow_dash=False,
|
|
421
|
+
dir_okay=True,
|
|
422
|
+
file_okay=False,
|
|
423
|
+
readable=True,
|
|
424
|
+
exists=True,
|
|
425
|
+
remote_okay=False,
|
|
426
|
+
),
|
|
427
|
+
)
|
|
428
|
+
@click.option("--version", help="Set the package version.")
|
|
429
|
+
@click.option("--replace", is_flag=True, help="Replace package archive, if present.")
|
|
430
|
+
@click.option(
|
|
431
|
+
"--output-dir",
|
|
432
|
+
type=ClickSmartPath(writable=True, file_okay=False, dir_okay=True),
|
|
433
|
+
help="Create the package archive in a specific directory.",
|
|
434
|
+
default=".",
|
|
435
|
+
show_default=True,
|
|
436
|
+
)
|
|
437
|
+
@click.pass_obj
|
|
438
|
+
def build_command(
|
|
439
|
+
app: ApplicationContext, package_directory: str, version: str, replace: bool, output_dir: str
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Build a package archive from a package directory.
|
|
442
|
+
|
|
443
|
+
This command processes a package directory, validates its content including the manifest,
|
|
444
|
+
and creates a versioned Corporate Memory package archive (.cpa) with the following naming
|
|
445
|
+
convention: {package_id}-v{version}.cpa
|
|
446
|
+
|
|
447
|
+
Package archives can be published to the marketplace using the `package publish` command.
|
|
448
|
+
"""
|
|
449
|
+
package_path = Path(package_directory)
|
|
450
|
+
package_version = PackageVersion.from_directory(package_path)
|
|
451
|
+
if version:
|
|
452
|
+
if version.startswith("v"):
|
|
453
|
+
version = version[1:]
|
|
454
|
+
package_version.manifest.package_version = SemanticVersion.parse(version)
|
|
455
|
+
version_str = str(package_version.manifest.package_version)
|
|
456
|
+
package_id = package_version.manifest.package_id
|
|
457
|
+
|
|
458
|
+
if output_dir is not None:
|
|
459
|
+
cpa_file = Path(
|
|
460
|
+
os.path.normpath(str(Path(output_dir) / f"{package_id}-v{version_str}.cpa"))
|
|
461
|
+
)
|
|
462
|
+
else:
|
|
463
|
+
cpa_file = Path(f"{package_id}-v{version_str}.cpa")
|
|
464
|
+
|
|
465
|
+
Path(cpa_file).parent.mkdir(exist_ok=True, parents=True)
|
|
466
|
+
|
|
467
|
+
if version_str.endswith("dirty"):
|
|
468
|
+
app.echo_warning(
|
|
469
|
+
"Dirty Repository: Your version strings ends with 'dirty'."
|
|
470
|
+
"This indicates an unclean repository."
|
|
471
|
+
)
|
|
472
|
+
if cpa_file.exists() and not replace:
|
|
473
|
+
raise CmemcError(
|
|
474
|
+
f"Package archive `{cpa_file}` already exists. Use `--replace` to overwrite."
|
|
475
|
+
)
|
|
476
|
+
app.echo_info(f"Building package archive `{cpa_file.name}` ... ", nl=False)
|
|
477
|
+
package_version.build_archive(archive=cpa_file)
|
|
478
|
+
app.echo_success("done")
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@click.command(cls=CmemcCommand, name="publish")
|
|
482
|
+
@click.argument(
|
|
483
|
+
"PACKAGE",
|
|
484
|
+
required=True,
|
|
485
|
+
type=ClickSmartPath(
|
|
486
|
+
allow_dash=False,
|
|
487
|
+
dir_okay=True,
|
|
488
|
+
readable=True,
|
|
489
|
+
exists=True,
|
|
490
|
+
remote_okay=True,
|
|
491
|
+
),
|
|
492
|
+
)
|
|
493
|
+
@click.option(
|
|
494
|
+
"--marketplace-url",
|
|
495
|
+
type=str,
|
|
496
|
+
help="Alternative Marketplace URL.",
|
|
497
|
+
default="https://marketplace.eccenca.dev/",
|
|
498
|
+
)
|
|
499
|
+
@click.pass_obj
|
|
500
|
+
def publish_command(app: ApplicationContext, package: str, marketplace_url: str) -> None:
|
|
501
|
+
"""Publish a package archive to the marketplace server."""
|
|
502
|
+
package_path = Path(package)
|
|
503
|
+
|
|
504
|
+
package_version = (
|
|
505
|
+
PackageVersion.from_directory(package_path)
|
|
506
|
+
if package_path.is_dir()
|
|
507
|
+
else PackageVersion.from_archive(package_path)
|
|
508
|
+
)
|
|
509
|
+
package_id = package_version.manifest.package_id
|
|
510
|
+
|
|
511
|
+
app.echo_info(f"Publishing package `{package_id}` ... ", nl=False)
|
|
512
|
+
|
|
513
|
+
if package_path.is_dir():
|
|
514
|
+
package_data = b"".join(PackageVersion.create_archive(package_path))
|
|
515
|
+
filename = f"{package_id}.cpa"
|
|
516
|
+
else:
|
|
517
|
+
package_data = package_path.read_bytes()
|
|
518
|
+
filename = package_path.name
|
|
519
|
+
|
|
520
|
+
if marketplace_url.endswith("/"):
|
|
521
|
+
marketplace_url = marketplace_url[:-1]
|
|
522
|
+
|
|
523
|
+
files = {"archive": (filename, package_data, "application/octet-stream")}
|
|
524
|
+
url = f"{marketplace_url}/api/packages/{package_id}/versions"
|
|
525
|
+
response = requests.post(
|
|
526
|
+
url=url,
|
|
527
|
+
timeout=30,
|
|
528
|
+
files=files,
|
|
529
|
+
headers={"accept": "application/json"},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
response.raise_for_status()
|
|
533
|
+
app.echo_success("done")
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@click.group(cls=CmemcGroup)
|
|
537
|
+
def package_group() -> CmemcGroup: # type: ignore[empty-body]
|
|
538
|
+
"""List, (un)install, export, create, or inspect packages."""
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
package_group.add_command(create_command)
|
|
542
|
+
package_group.add_command(inspect_command)
|
|
543
|
+
package_group.add_command(list_command)
|
|
544
|
+
package_group.add_command(install_command)
|
|
545
|
+
package_group.add_command(uninstall_command)
|
|
546
|
+
package_group.add_command(export_command)
|
|
547
|
+
package_group.add_command(build_command)
|
|
548
|
+
package_group.add_command(publish_command)
|