@adobe/aio-cli-plugin-app 10.1.1 → 10.2.1
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.
- package/README.md +1 -1
- package/oclif.manifest.json +88 -1
- package/package.json +10 -8
- package/schema/app.config.yaml.schema.json +157 -0
- package/schema/config.schema.json +1 -1
- package/schema/deploy.yaml.schema.json +107 -0
- package/schema/index.js +19 -0
- package/src/BaseCommand.js +4 -0
- package/src/TemplatesCommand.js +2 -0
- package/src/commands/app/deploy.js +28 -1
- package/src/commands/app/install.js +166 -0
- package/src/commands/app/pack.js +296 -0
- package/src/commands/app/test.js +1 -0
- package/src/lib/app-helper.js +25 -0
- package/src/lib/defaults.js +2 -0
- package/src/lib/deploy-actions.js +6 -1
- package/src/lib/import-helper.js +3 -18
- package/src/lib/install-helper.js +37 -0
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ DESCRIPTION
|
|
|
71
71
|
Create, run, test, and deploy Adobe I/O Apps
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
_See code: [src/commands/app/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/10.
|
|
74
|
+
_See code: [src/commands/app/index.js](https://github.com/adobe/aio-cli-plugin-app/blob/10.2.1/src/commands/app/index.js)_
|
|
75
75
|
|
|
76
76
|
## `aio app add`
|
|
77
77
|
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "10.
|
|
2
|
+
"version": "10.2.1",
|
|
3
3
|
"commands": {
|
|
4
4
|
"app:build": {
|
|
5
5
|
"id": "app:build",
|
|
@@ -237,6 +237,13 @@
|
|
|
237
237
|
"type": "boolean",
|
|
238
238
|
"description": "[default: true] Update log forwarding configuration on server",
|
|
239
239
|
"allowNo": true
|
|
240
|
+
},
|
|
241
|
+
"feature-event-hooks": {
|
|
242
|
+
"name": "feature-event-hooks",
|
|
243
|
+
"type": "boolean",
|
|
244
|
+
"description": "[default: false] Enable event hooks feature",
|
|
245
|
+
"hidden": true,
|
|
246
|
+
"allowNo": true
|
|
240
247
|
}
|
|
241
248
|
},
|
|
242
249
|
"args": {}
|
|
@@ -492,6 +499,46 @@
|
|
|
492
499
|
}
|
|
493
500
|
}
|
|
494
501
|
},
|
|
502
|
+
"app:install": {
|
|
503
|
+
"id": "app:install",
|
|
504
|
+
"description": "(Pre-release) This command will support installing apps packaged by '<%= config.bin %> app pack'.\n",
|
|
505
|
+
"strict": true,
|
|
506
|
+
"pluginName": "@adobe/aio-cli-plugin-app",
|
|
507
|
+
"pluginAlias": "@adobe/aio-cli-plugin-app",
|
|
508
|
+
"pluginType": "core",
|
|
509
|
+
"hidden": true,
|
|
510
|
+
"aliases": [],
|
|
511
|
+
"flags": {
|
|
512
|
+
"verbose": {
|
|
513
|
+
"name": "verbose",
|
|
514
|
+
"type": "boolean",
|
|
515
|
+
"char": "v",
|
|
516
|
+
"description": "Verbose output",
|
|
517
|
+
"allowNo": false
|
|
518
|
+
},
|
|
519
|
+
"version": {
|
|
520
|
+
"name": "version",
|
|
521
|
+
"type": "boolean",
|
|
522
|
+
"description": "Show version",
|
|
523
|
+
"allowNo": false
|
|
524
|
+
},
|
|
525
|
+
"output": {
|
|
526
|
+
"name": "output",
|
|
527
|
+
"type": "option",
|
|
528
|
+
"char": "o",
|
|
529
|
+
"description": "The packaged app output folder path",
|
|
530
|
+
"multiple": false,
|
|
531
|
+
"default": "."
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
"args": {
|
|
535
|
+
"path": {
|
|
536
|
+
"name": "path",
|
|
537
|
+
"description": "Path to the app package to install",
|
|
538
|
+
"required": true
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
},
|
|
495
542
|
"app:logs": {
|
|
496
543
|
"id": "app:logs",
|
|
497
544
|
"description": "Fetch logs for an Adobe I/O App\n",
|
|
@@ -572,6 +619,46 @@
|
|
|
572
619
|
},
|
|
573
620
|
"args": {}
|
|
574
621
|
},
|
|
622
|
+
"app:pack": {
|
|
623
|
+
"id": "app:pack",
|
|
624
|
+
"description": "(Pre-release) This command will support packaging apps for redistribution.\n",
|
|
625
|
+
"strict": true,
|
|
626
|
+
"pluginName": "@adobe/aio-cli-plugin-app",
|
|
627
|
+
"pluginAlias": "@adobe/aio-cli-plugin-app",
|
|
628
|
+
"pluginType": "core",
|
|
629
|
+
"hidden": true,
|
|
630
|
+
"aliases": [],
|
|
631
|
+
"flags": {
|
|
632
|
+
"verbose": {
|
|
633
|
+
"name": "verbose",
|
|
634
|
+
"type": "boolean",
|
|
635
|
+
"char": "v",
|
|
636
|
+
"description": "Verbose output",
|
|
637
|
+
"allowNo": false
|
|
638
|
+
},
|
|
639
|
+
"version": {
|
|
640
|
+
"name": "version",
|
|
641
|
+
"type": "boolean",
|
|
642
|
+
"description": "Show version",
|
|
643
|
+
"allowNo": false
|
|
644
|
+
},
|
|
645
|
+
"output": {
|
|
646
|
+
"name": "output",
|
|
647
|
+
"type": "option",
|
|
648
|
+
"char": "o",
|
|
649
|
+
"description": "The packaged app output file path",
|
|
650
|
+
"multiple": false,
|
|
651
|
+
"default": "app.zip"
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
"args": {
|
|
655
|
+
"path": {
|
|
656
|
+
"name": "path",
|
|
657
|
+
"description": "Path to the app directory to package",
|
|
658
|
+
"default": "."
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
},
|
|
575
662
|
"app:run": {
|
|
576
663
|
"id": "app:run",
|
|
577
664
|
"description": "Run an Adobe I/O App",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aio-cli-plugin-app",
|
|
3
3
|
"description": "Create, Build and Deploy Adobe I/O Applications",
|
|
4
|
-
"version": "10.
|
|
4
|
+
"version": "10.2.1",
|
|
5
5
|
"author": "Adobe Inc.",
|
|
6
6
|
"bugs": "https://github.com/adobe/aio-cli-plugin-app/issues",
|
|
7
7
|
"dependencies": {
|
|
@@ -14,35 +14,38 @@
|
|
|
14
14
|
"@adobe/aio-lib-ims": "^6.0.0",
|
|
15
15
|
"@adobe/aio-lib-runtime": "^5.0.0",
|
|
16
16
|
"@adobe/aio-lib-templates": "^2.2.0",
|
|
17
|
-
"@adobe/aio-lib-web": "^6.0
|
|
17
|
+
"@adobe/aio-lib-web": "^6.1.0",
|
|
18
18
|
"@adobe/generator-aio-app": "^5.1.0",
|
|
19
19
|
"@adobe/generator-app-common-lib": "^0.3.3",
|
|
20
20
|
"@adobe/inquirer-table-checkbox": "^1.2.0",
|
|
21
21
|
"@oclif/core": "^1.15.0",
|
|
22
22
|
"@parcel/core": "^2.7.0",
|
|
23
23
|
"@parcel/reporter-cli": "^2.7.0",
|
|
24
|
-
"ajv": "^
|
|
24
|
+
"ajv": "^8",
|
|
25
|
+
"ajv-formats": "^2.1.1",
|
|
26
|
+
"archiver": "^5.3.1",
|
|
25
27
|
"chalk": "^4",
|
|
26
28
|
"chokidar": "^3.5.2",
|
|
27
29
|
"debug": "^4.1.1",
|
|
28
30
|
"dedent-js": "^1.0.1",
|
|
29
31
|
"dotenv": "^16",
|
|
30
32
|
"execa": "^5.0.0",
|
|
31
|
-
"fs-extra": "^
|
|
33
|
+
"fs-extra": "^11.1.1",
|
|
32
34
|
"get-port": "^5",
|
|
33
35
|
"hjson": "^3.2.1",
|
|
34
36
|
"http-terminator": "^3",
|
|
35
37
|
"hyperlinker": "^1.0.0",
|
|
36
38
|
"inquirer": "^8",
|
|
37
|
-
"js-yaml": "^4",
|
|
39
|
+
"js-yaml": "^4.1.0",
|
|
38
40
|
"lodash.clonedeep": "^4.5.0",
|
|
39
41
|
"node-fetch": "^2.6.7",
|
|
40
42
|
"ora": "^5",
|
|
41
43
|
"pure-http": "^3",
|
|
42
44
|
"serve-static": "^1.14.1",
|
|
43
45
|
"term-size": "^2.2.1",
|
|
46
|
+
"unzipper": "^0.10.11",
|
|
44
47
|
"upath": "^2",
|
|
45
|
-
"which": "^
|
|
48
|
+
"which": "^3.0.0",
|
|
46
49
|
"yeoman-environment": "^3.2.0"
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
@@ -61,7 +64,6 @@
|
|
|
61
64
|
"eslint-plugin-node": "^11.1.0",
|
|
62
65
|
"eslint-plugin-promise": "^6.0.0",
|
|
63
66
|
"jest": "^29.5.0",
|
|
64
|
-
"jest-plugin-fs": "^2.9.0",
|
|
65
67
|
"nock": "^13.2.9",
|
|
66
68
|
"oclif": "^3.2.0",
|
|
67
69
|
"stdout-stderr": "^0.1.9"
|
|
@@ -75,7 +77,7 @@
|
|
|
75
77
|
"bin/openwhisk-standalone-config/",
|
|
76
78
|
"oclif.manifest.json",
|
|
77
79
|
"src",
|
|
78
|
-
"schema
|
|
80
|
+
"schema"
|
|
79
81
|
],
|
|
80
82
|
"homepage": "https://github.com/adobe/aio-cli-plugin-app",
|
|
81
83
|
"keywords": [
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://adobe.io/schemas/app-builder/app.config.yaml.json/v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"application": { "$ref": "#/definitions/application" },
|
|
7
|
+
"extensions": { "$ref": "#/definitions/extensions" }
|
|
8
|
+
},
|
|
9
|
+
"oneOf": [
|
|
10
|
+
{
|
|
11
|
+
"required": ["application"],
|
|
12
|
+
"allOf": [
|
|
13
|
+
{ "not": { "required": ["extensions"] } }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"required": ["extensions"],
|
|
18
|
+
"allOf": [
|
|
19
|
+
{ "not": { "required": ["application"] } }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"definitions": {
|
|
24
|
+
"application": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"runtimeManifest": { "$ref": "#/definitions/runtimeManifest" },
|
|
28
|
+
"actions": { "type": "string" },
|
|
29
|
+
"unitTest": { "type": "string" },
|
|
30
|
+
"e2eTest": { "type": "string" },
|
|
31
|
+
"dist": { "type": "string" },
|
|
32
|
+
"tvmurl": { "type": "string" },
|
|
33
|
+
"awsaccesskeyid": { "type": "string" },
|
|
34
|
+
"awssecretaccesskey": { "type": "string" },
|
|
35
|
+
"s3bucket": { "type": "string" },
|
|
36
|
+
"events": { "type": "object" },
|
|
37
|
+
"hostname": { "type": "string" },
|
|
38
|
+
"htmlcacheduration": { "type": "number" },
|
|
39
|
+
"jscacheduration": { "type": "number" },
|
|
40
|
+
"csscacheduration": { "type": "number" },
|
|
41
|
+
"imagecacheduration": { "type": "number" },
|
|
42
|
+
"hooks": { "$ref": "#/definitions/hooks" },
|
|
43
|
+
"web": { "$ref": "#/definitions/web" }
|
|
44
|
+
},
|
|
45
|
+
"required": ["runtimeManifest"]
|
|
46
|
+
},
|
|
47
|
+
"extensions": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"patternProperties": {
|
|
50
|
+
"^[A-Za-z0-9-_/\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$": {
|
|
51
|
+
"$ref": "#/definitions/extension"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"additionalProperties": false
|
|
55
|
+
},
|
|
56
|
+
"extension": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"properties": {
|
|
59
|
+
"$include": { "type": "string" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"web": {
|
|
63
|
+
"anyOf": [
|
|
64
|
+
{
|
|
65
|
+
"type": "string"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"response-headers": { "type": "object" }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"runtimeManifest": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"properties": {
|
|
78
|
+
"packages": { "$ref": "#/definitions/packages" }
|
|
79
|
+
},
|
|
80
|
+
"required": ["packages"]
|
|
81
|
+
},
|
|
82
|
+
"packages": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"patternProperties": {
|
|
85
|
+
"^[A-Za-z0-9-_\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$": {
|
|
86
|
+
"$ref": "#/definitions/package"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"additionalProperties": false
|
|
90
|
+
},
|
|
91
|
+
"package": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {
|
|
94
|
+
"license": { "type": "string" },
|
|
95
|
+
"actions": { "$ref": "#/definitions/actions" }
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"actions": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"patternProperties": {
|
|
101
|
+
"^[A-Za-z0-9-_\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$": {
|
|
102
|
+
"$ref": "#/definitions/action"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"additionalProperties": false
|
|
106
|
+
},
|
|
107
|
+
"action": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {
|
|
110
|
+
"function": { "type": "string" },
|
|
111
|
+
"web": { "type": "string" },
|
|
112
|
+
"runtime": { "type": "string" },
|
|
113
|
+
"inputs": { "$ref": "#/definitions/inputs" },
|
|
114
|
+
"annotations": { "$ref": "#/definitions/annotations" }
|
|
115
|
+
},
|
|
116
|
+
"required": ["function", "runtime"]
|
|
117
|
+
},
|
|
118
|
+
"inputs": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"patternProperties": {
|
|
121
|
+
"^[A-Za-z0-9-_\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$": {
|
|
122
|
+
"type": ["string", "boolean"]
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": false
|
|
126
|
+
},
|
|
127
|
+
"annotations": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"patternProperties": {
|
|
130
|
+
"^[A-Za-z0-9-_\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$": {
|
|
131
|
+
"type": ["string", "boolean"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"additionalProperties": false
|
|
135
|
+
},
|
|
136
|
+
"hooks": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"properties": {
|
|
139
|
+
"pre-app-build": { "type": "string" },
|
|
140
|
+
"post-app-build": { "type": "string" },
|
|
141
|
+
"build-actions": { "type": "string" },
|
|
142
|
+
"build-static": { "type": "string" },
|
|
143
|
+
"pre-app-deploy": { "type": "string" },
|
|
144
|
+
"post-app-deploy": { "type": "string" },
|
|
145
|
+
"deploy-actions": { "type": "string" },
|
|
146
|
+
"deploy-static": { "type": "string" },
|
|
147
|
+
"pre-app-undeploy": { "type": "string" },
|
|
148
|
+
"post-app-undeploy": { "type": "string" },
|
|
149
|
+
"undeploy-actions": { "type": "string" },
|
|
150
|
+
"undeploy-static": { "type": "string" },
|
|
151
|
+
"pre-app-run": { "type": "string" },
|
|
152
|
+
"post-app-run": { "type": "string" },
|
|
153
|
+
"serve-static": { "type": "string" }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://adobe.io/schemas/app-builder/deploy.yaml.json/v1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"application": { "$ref": "#/definitions/application" },
|
|
7
|
+
"workspaces": { "$ref": "#/definitions/workspaces" },
|
|
8
|
+
"meshConfig": { "$ref": "#/definitions/meshConfig" },
|
|
9
|
+
"extensions": { "$ref": "#/definitions/extensions" },
|
|
10
|
+
"apis": { "$ref": "#/definitions/apis" },
|
|
11
|
+
"runtime": {
|
|
12
|
+
"type": "boolean",
|
|
13
|
+
"default": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"required": ["application", "workspaces", "runtime"],
|
|
17
|
+
"definitions": {
|
|
18
|
+
"application": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"id": { "type": "string" },
|
|
22
|
+
"version": { "type": "string" }
|
|
23
|
+
},
|
|
24
|
+
"required": ["id", "version"]
|
|
25
|
+
},
|
|
26
|
+
"workspaces": {
|
|
27
|
+
"type": "array",
|
|
28
|
+
"items": { "$ref": "#/definitions/workspace" },
|
|
29
|
+
"default": []
|
|
30
|
+
},
|
|
31
|
+
"meshConfig": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"meshConfig": { "$ref": "#/definitions/meshConfigNested" },
|
|
35
|
+
"meshId": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
"lastUpdatedBy": { "type": "object" },
|
|
39
|
+
"lastUpdated": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"pattern": "(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2}(?:\\.\\d*)?)((-(\\d{2}):(\\d{2})|Z)?)"
|
|
42
|
+
},
|
|
43
|
+
"meshStatus": { "type": "string" }
|
|
44
|
+
},
|
|
45
|
+
"required": ["meshConfig", "meshId"]
|
|
46
|
+
},
|
|
47
|
+
"meshConfigNested": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"sources": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": { "$ref": "#/definitions/meshConfigSource" },
|
|
53
|
+
"default": []
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"meshConfigSource": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"name": { "type": "string" },
|
|
61
|
+
"handler": { "$ref": "#/definitions/meshConfigSourceHandler" }
|
|
62
|
+
},
|
|
63
|
+
"required": ["name", "handler"]
|
|
64
|
+
},
|
|
65
|
+
"meshConfigSourceHandler": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"graphql": { "$ref": "#/definitions/graphql" }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"graphql": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"handler": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"format": "uri"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"workspace": {
|
|
81
|
+
"type": "string",
|
|
82
|
+
"pattern": "^[A-Za-z0-9\u00C0-\u00D6\u00D8-\u00f6\u00f8-\u00ff]{1,20}$"
|
|
83
|
+
},
|
|
84
|
+
"extensions": {
|
|
85
|
+
"type": "array",
|
|
86
|
+
"items": { "$ref": "#/definitions/extension" }
|
|
87
|
+
},
|
|
88
|
+
"extension": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"properties": {
|
|
91
|
+
"extensionPointId": { "type": "string" }
|
|
92
|
+
},
|
|
93
|
+
"required": ["extensionPointId"]
|
|
94
|
+
},
|
|
95
|
+
"apis": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": { "$ref": "#/definitions/api" }
|
|
98
|
+
},
|
|
99
|
+
"api": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": {
|
|
102
|
+
"code": { "type": "string" }
|
|
103
|
+
},
|
|
104
|
+
"required": ["code"]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
package/schema/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { USER_CONFIG_FILE, DEPLOY_CONFIG_FILE, IMPORT_CONFIG_FILE } = require('../src/lib/defaults')
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
[IMPORT_CONFIG_FILE]: require('./config.schema.json'),
|
|
17
|
+
[USER_CONFIG_FILE]: require('./app.config.yaml.schema.json'),
|
|
18
|
+
[DEPLOY_CONFIG_FILE]: require('./deploy.yaml.schema.json')
|
|
19
|
+
}
|
package/src/BaseCommand.js
CHANGED
|
@@ -165,6 +165,10 @@ class BaseCommand extends Command {
|
|
|
165
165
|
get appVersion () {
|
|
166
166
|
return this.pjson.version
|
|
167
167
|
}
|
|
168
|
+
|
|
169
|
+
preRelease () {
|
|
170
|
+
this.log(chalk.yellow('Pre-release warning: This command is in pre-release, and not suitable for production.'))
|
|
171
|
+
}
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
BaseCommand.flags = {
|
package/src/TemplatesCommand.js
CHANGED
|
@@ -152,6 +152,8 @@ class TemplatesCommand extends AddCommand {
|
|
|
152
152
|
|
|
153
153
|
if (templates.length <= 0) {
|
|
154
154
|
aioLogger.debug('installTemplates: standalone-app')
|
|
155
|
+
// technically runHook can quietly fail, but we are choosing to ignore it as these are telemetry events
|
|
156
|
+
// and not mission critical
|
|
155
157
|
await this.config.runHook('telemetry', { data: 'installTemplates:standalone-app' })
|
|
156
158
|
} else {
|
|
157
159
|
aioLogger.debug(`installTemplates: ${templates}`)
|
|
@@ -143,6 +143,15 @@ class Deploy extends BuildCommand {
|
|
|
143
143
|
|
|
144
144
|
try {
|
|
145
145
|
await runScript(config.hooks['pre-app-deploy'])
|
|
146
|
+
|
|
147
|
+
if (flags['feature-event-hooks']) {
|
|
148
|
+
this.log('feature-event-hooks is enabled, running pre-deploy-event-reg hook')
|
|
149
|
+
const hookResults = await this.config.runHook('pre-deploy-event-reg', { appConfig: config })
|
|
150
|
+
if (hookResults?.failures?.length > 0) {
|
|
151
|
+
// output should be "Error : <plugin-name> : <error-message>\n" for each failure
|
|
152
|
+
this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
146
155
|
} catch (err) {
|
|
147
156
|
this.log(err)
|
|
148
157
|
}
|
|
@@ -158,11 +167,15 @@ class Deploy extends BuildCommand {
|
|
|
158
167
|
try {
|
|
159
168
|
const script = await runScript(config.hooks['deploy-actions'])
|
|
160
169
|
if (!script) {
|
|
161
|
-
await this.config.runHook('deploy-actions', {
|
|
170
|
+
const hookResults = await this.config.runHook('deploy-actions', {
|
|
162
171
|
appConfig: config,
|
|
163
172
|
filterEntities: filterActions || [],
|
|
164
173
|
isLocalDev: false
|
|
165
174
|
})
|
|
175
|
+
if (hookResults?.failures?.length > 0) {
|
|
176
|
+
// output should be "Error : <plugin-name> : <error-message>\n" for each failure
|
|
177
|
+
this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
|
|
178
|
+
}
|
|
166
179
|
deployedRuntimeEntities = await rtLib.deployActions(config, { filterEntities }, onProgress)
|
|
167
180
|
}
|
|
168
181
|
|
|
@@ -239,6 +252,14 @@ class Deploy extends BuildCommand {
|
|
|
239
252
|
|
|
240
253
|
try {
|
|
241
254
|
await runScript(config.hooks['post-app-deploy'])
|
|
255
|
+
if (flags['feature-event-hooks']) {
|
|
256
|
+
this.log('feature-event-hooks is enabled, running post-deploy-event-reg hook')
|
|
257
|
+
const hookResults = await this.config.runHook('post-deploy-event-reg', { appConfig: config })
|
|
258
|
+
if (hookResults?.failures?.length > 0) {
|
|
259
|
+
// output should be "Error : <plugin-name> : <error-message>\n" for each failure
|
|
260
|
+
this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
|
|
261
|
+
}
|
|
262
|
+
}
|
|
242
263
|
} catch (err) {
|
|
243
264
|
this.log(err)
|
|
244
265
|
}
|
|
@@ -341,6 +362,12 @@ Deploy.flags = {
|
|
|
341
362
|
description: '[default: true] Update log forwarding configuration on server',
|
|
342
363
|
default: true,
|
|
343
364
|
allowNo: true
|
|
365
|
+
}),
|
|
366
|
+
'feature-event-hooks': Flags.boolean({
|
|
367
|
+
description: '[default: false] Enable event hooks feature',
|
|
368
|
+
default: false,
|
|
369
|
+
allowNo: true,
|
|
370
|
+
hidden: true
|
|
344
371
|
})
|
|
345
372
|
}
|
|
346
373
|
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const BaseCommand = require('../../BaseCommand')
|
|
14
|
+
const { Flags } = require('@oclif/core')
|
|
15
|
+
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:install', { provider: 'debug' })
|
|
16
|
+
const path = require('node:path')
|
|
17
|
+
const fs = require('fs-extra')
|
|
18
|
+
const execa = require('execa')
|
|
19
|
+
const unzipper = require('unzipper')
|
|
20
|
+
const { validateJsonWithSchema } = require('../../lib/install-helper')
|
|
21
|
+
const jsYaml = require('js-yaml')
|
|
22
|
+
const { USER_CONFIG_FILE, DEPLOY_CONFIG_FILE } = require('../../lib/defaults')
|
|
23
|
+
const ora = require('ora')
|
|
24
|
+
const chalk = require('chalk')
|
|
25
|
+
|
|
26
|
+
class InstallCommand extends BaseCommand {
|
|
27
|
+
async run () {
|
|
28
|
+
const { args, flags } = await this.parse(InstallCommand)
|
|
29
|
+
|
|
30
|
+
this.preRelease()
|
|
31
|
+
|
|
32
|
+
aioLogger.debug(`flags: ${JSON.stringify(flags, null, 2)}`)
|
|
33
|
+
aioLogger.debug(`args: ${JSON.stringify(args, null, 2)}`)
|
|
34
|
+
|
|
35
|
+
// resolve to absolute path before any chdir
|
|
36
|
+
args.path = path.resolve(args.path)
|
|
37
|
+
aioLogger.debug(`args.path (resolved): ${args.path}`)
|
|
38
|
+
|
|
39
|
+
let outputPath = flags.output
|
|
40
|
+
// change the cwd if necessary
|
|
41
|
+
if (outputPath !== '.') {
|
|
42
|
+
outputPath = path.resolve(flags.output)
|
|
43
|
+
// TODO: confirm if dir exists for overwrite
|
|
44
|
+
await fs.ensureDir(outputPath)
|
|
45
|
+
process.chdir(outputPath)
|
|
46
|
+
aioLogger.debug(`changed current working directory to: ${outputPath}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
await this.validateZipDirectoryStructure(args.path)
|
|
51
|
+
await this.unzipFile(args.path, outputPath)
|
|
52
|
+
await this.validateConfig(outputPath, USER_CONFIG_FILE)
|
|
53
|
+
await this.validateConfig(outputPath, DEPLOY_CONFIG_FILE)
|
|
54
|
+
await this.npmInstall(flags.verbose)
|
|
55
|
+
await this.runTests()
|
|
56
|
+
this.spinner.succeed('Install done.')
|
|
57
|
+
} catch (e) {
|
|
58
|
+
this.spinner.fail(e.message)
|
|
59
|
+
this.error(flags.verbose ? e : e.message)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get spinner () {
|
|
64
|
+
if (!this._spinner) {
|
|
65
|
+
this._spinner = ora()
|
|
66
|
+
}
|
|
67
|
+
return this._spinner
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
diffArray (expected, actual) {
|
|
71
|
+
const _expected = expected ?? []
|
|
72
|
+
const _actual = actual ?? []
|
|
73
|
+
return _expected.filter(item => !_actual.includes(item))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async validateZipDirectoryStructure (zipFilePath) {
|
|
77
|
+
aioLogger.debug(`validateZipDirectoryStructure: ${zipFilePath}`)
|
|
78
|
+
|
|
79
|
+
const expectedFiles = [USER_CONFIG_FILE, DEPLOY_CONFIG_FILE, 'package.json']
|
|
80
|
+
const foundFiles = []
|
|
81
|
+
|
|
82
|
+
this.spinner.start(`Validating integrity of app package at ${zipFilePath}...`)
|
|
83
|
+
|
|
84
|
+
const zip = fs.createReadStream(zipFilePath).pipe(unzipper.Parse({ forceStream: true }))
|
|
85
|
+
for await (const entry of zip) {
|
|
86
|
+
const fileName = entry.path
|
|
87
|
+
|
|
88
|
+
if (expectedFiles.includes(fileName)) {
|
|
89
|
+
foundFiles.push(fileName)
|
|
90
|
+
}
|
|
91
|
+
entry.autodrain()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const diff = this.diffArray(expectedFiles, foundFiles)
|
|
95
|
+
if (diff.length > 0) {
|
|
96
|
+
throw new Error(`The app package ${zipFilePath} is missing these files: ${JSON.stringify(diff, null, 2)}`)
|
|
97
|
+
}
|
|
98
|
+
this.spinner.succeed(`Validated integrity of app package at ${zipFilePath}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async unzipFile (zipFilePath, destFolderPath) {
|
|
102
|
+
aioLogger.debug(`unzipFile: ${zipFilePath} to be extracted to ${destFolderPath}`)
|
|
103
|
+
|
|
104
|
+
this.spinner.start(`Extracting app package to ${destFolderPath}...`)
|
|
105
|
+
return unzipper.Open.file(zipFilePath)
|
|
106
|
+
.then((d) => d.extract({ path: destFolderPath }))
|
|
107
|
+
.then(() => this.spinner.succeed(`Extracted app package to ${destFolderPath}`))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async validateConfig (outputPath, configFileName, configFilePath = path.join(outputPath, configFileName)) {
|
|
111
|
+
this.spinner.start(`Validating ${configFileName}...`)
|
|
112
|
+
aioLogger.debug(`validateConfig: ${configFileName} at ${configFilePath}`)
|
|
113
|
+
|
|
114
|
+
const configFileJson = jsYaml.load(fs.readFileSync(configFilePath).toString())
|
|
115
|
+
const { valid, errors } = validateJsonWithSchema(configFileJson, configFileName)
|
|
116
|
+
if (!valid) {
|
|
117
|
+
throw new Error(`Missing or invalid keys in ${configFileName}: ${JSON.stringify(errors, null, 2)}`)
|
|
118
|
+
} else {
|
|
119
|
+
this.spinner.succeed(`Validated ${configFileName}`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async npmInstall (isVerbose) {
|
|
124
|
+
this.spinner.start('Running npm install...')
|
|
125
|
+
const stdio = isVerbose ? 'inherit' : 'ignore'
|
|
126
|
+
return execa('npm', ['install'], { stdio })
|
|
127
|
+
.then(() => {
|
|
128
|
+
this.spinner.succeed('Ran npm install')
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async runTests (isVerbose) {
|
|
133
|
+
this.spinner.start('Running app tests...')
|
|
134
|
+
return this.config.runCommand('app:test').then((result) => {
|
|
135
|
+
if (result === 0) { // success
|
|
136
|
+
this.spinner.succeed('App tests passed')
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error('App tests failed')
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
InstallCommand.hidden = true // hide from help for pre-release
|
|
145
|
+
|
|
146
|
+
InstallCommand.description = chalk.yellow(`(Pre-release) This command will support installing apps packaged by '<%= config.bin %> app pack'.
|
|
147
|
+
`)
|
|
148
|
+
|
|
149
|
+
InstallCommand.flags = {
|
|
150
|
+
...BaseCommand.flags,
|
|
151
|
+
output: Flags.string({
|
|
152
|
+
description: 'The packaged app output folder path',
|
|
153
|
+
char: 'o',
|
|
154
|
+
default: '.'
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
InstallCommand.args = [
|
|
159
|
+
{
|
|
160
|
+
name: 'path',
|
|
161
|
+
description: 'Path to the app package to install',
|
|
162
|
+
required: true
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
module.exports = InstallCommand
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const BaseCommand = require('../../BaseCommand')
|
|
13
|
+
const { Flags } = require('@oclif/core')
|
|
14
|
+
const path = require('node:path')
|
|
15
|
+
const fs = require('fs-extra')
|
|
16
|
+
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:pack', { provider: 'debug' })
|
|
17
|
+
const archiver = require('archiver')
|
|
18
|
+
const yaml = require('js-yaml')
|
|
19
|
+
const execa = require('execa')
|
|
20
|
+
const { loadConfigFile, writeFile } = require('../../lib/import-helper')
|
|
21
|
+
const { getObjectValue } = require('../../lib/app-helper')
|
|
22
|
+
const ora = require('ora')
|
|
23
|
+
const chalk = require('chalk')
|
|
24
|
+
|
|
25
|
+
const DEFAULTS = {
|
|
26
|
+
OUTPUT_ZIP_FILE: 'app.zip',
|
|
27
|
+
ARTIFACTS_FOLDER: 'app-package',
|
|
28
|
+
DEPLOY_YAML_FILE: 'deploy.yaml'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class Pack extends BaseCommand {
|
|
32
|
+
async run () {
|
|
33
|
+
const { args, flags } = await this.parse(Pack)
|
|
34
|
+
|
|
35
|
+
this.preRelease()
|
|
36
|
+
|
|
37
|
+
aioLogger.debug(`flags: ${JSON.stringify(flags, null, 2)}`)
|
|
38
|
+
aioLogger.debug(`args: ${JSON.stringify(args, null, 2)}`)
|
|
39
|
+
|
|
40
|
+
const appConfig = this.getFullConfig()
|
|
41
|
+
|
|
42
|
+
// resolve to absolute path before any chdir
|
|
43
|
+
const outputZipFile = path.resolve(flags.output)
|
|
44
|
+
|
|
45
|
+
// change the cwd if necessary
|
|
46
|
+
if (args.path !== '.') {
|
|
47
|
+
const resolvedPath = path.resolve(args.path)
|
|
48
|
+
process.chdir(resolvedPath)
|
|
49
|
+
aioLogger.debug(`changed current working directory to: ${resolvedPath}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// 1. create artifacts phase
|
|
54
|
+
this.spinner.start(`Creating package artifacts folder '${DEFAULTS.ARTIFACTS_FOLDER}'...`)
|
|
55
|
+
await fs.emptyDir(DEFAULTS.ARTIFACTS_FOLDER)
|
|
56
|
+
this.spinner.succeed(`Created package artifacts folder '${DEFAULTS.ARTIFACTS_FOLDER}'`)
|
|
57
|
+
|
|
58
|
+
// ACNA-2038
|
|
59
|
+
// not artifacts folder should exist before we fire the event
|
|
60
|
+
await this.config.runHook('pre-pack', { appConfig, artifactsFolder: DEFAULTS.ARTIFACTS_FOLDER })
|
|
61
|
+
|
|
62
|
+
// 2. copy files to package phase
|
|
63
|
+
this.spinner.start('Copying project files...')
|
|
64
|
+
const fileList = await this.filesToPack([flags.output])
|
|
65
|
+
await this.copyPackageFiles(DEFAULTS.ARTIFACTS_FOLDER, fileList)
|
|
66
|
+
this.spinner.succeed('Copied project files')
|
|
67
|
+
|
|
68
|
+
// 3. add/modify artifacts phase
|
|
69
|
+
this.spinner.start('Creating configuration files...')
|
|
70
|
+
await this.createDeployYamlFile(appConfig)
|
|
71
|
+
this.spinner.succeed('Created configuration files')
|
|
72
|
+
|
|
73
|
+
this.spinner.start('Adding code-download annotations...')
|
|
74
|
+
await this.addCodeDownloadAnnotation(appConfig)
|
|
75
|
+
this.spinner.succeed('Added code-download annotations')
|
|
76
|
+
|
|
77
|
+
// doing this before zip so other things can be added to the zip
|
|
78
|
+
await this.config.runHook('post-pack', { appConfig, artifactsFolder: DEFAULTS.ARTIFACTS_FOLDER })
|
|
79
|
+
|
|
80
|
+
// 4. zip package phase
|
|
81
|
+
this.spinner.start(`Zipping package artifacts folder '${DEFAULTS.ARTIFACTS_FOLDER}' to '${outputZipFile}'...`)
|
|
82
|
+
await fs.remove(outputZipFile)
|
|
83
|
+
await this.zipHelper(DEFAULTS.ARTIFACTS_FOLDER, outputZipFile)
|
|
84
|
+
this.spinner.succeed(`Zipped package artifacts folder '${DEFAULTS.ARTIFACTS_FOLDER}' to '${outputZipFile}'`)
|
|
85
|
+
} catch (e) {
|
|
86
|
+
this.spinner.fail(e.message)
|
|
87
|
+
this.error(flags.verbose ? e : e.message)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.spinner.succeed('Packaging done.')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get spinner () {
|
|
94
|
+
if (!this._spinner) {
|
|
95
|
+
this._spinner = ora()
|
|
96
|
+
}
|
|
97
|
+
return this._spinner
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates the deploy.yaml file
|
|
102
|
+
*
|
|
103
|
+
* @param {object} appConfig the app's configuration file
|
|
104
|
+
*/
|
|
105
|
+
async createDeployYamlFile (appConfig) {
|
|
106
|
+
// get extensions
|
|
107
|
+
let extensions
|
|
108
|
+
if (appConfig.implements?.filter(item => item !== 'application').length > 0) {
|
|
109
|
+
extensions = appConfig.implements.map(ext => ({ extensionPointId: ext }))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// get workspaces
|
|
113
|
+
let workspaces
|
|
114
|
+
if (appConfig.aio?.project?.workspace?.name) {
|
|
115
|
+
workspaces = []
|
|
116
|
+
workspaces.push(appConfig.aio?.project?.workspace?.name)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// get apis
|
|
120
|
+
let apis
|
|
121
|
+
if (appConfig.aio?.project?.workspace?.details?.services?.length > 0) {
|
|
122
|
+
apis = appConfig.aio.project.workspace.details.services.map(service => ({ code: service.code }))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// read name and version from package.json
|
|
126
|
+
const application = {
|
|
127
|
+
id: appConfig.packagejson.name,
|
|
128
|
+
version: appConfig.packagejson.version
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let meshConfig
|
|
132
|
+
// ACNA-2041
|
|
133
|
+
// get the mesh config by running the `aio api-mesh:get` command (if available)
|
|
134
|
+
// in the interim, we need to process the output to get the proper json config
|
|
135
|
+
// TODO: send a PR to their plugin to have a `--json` flag
|
|
136
|
+
const command = await this.config.findCommand('api-mesh:get')
|
|
137
|
+
if (command) {
|
|
138
|
+
this.spinner.start('Getting api-mesh config...')
|
|
139
|
+
const { stdout } = await execa('aio', ['api-mesh', 'get'], { cwd: process.cwd() })
|
|
140
|
+
// until we get the --json flag, we parse the output
|
|
141
|
+
const idx = stdout.indexOf('{')
|
|
142
|
+
meshConfig = JSON.parse(stdout.substring(idx))
|
|
143
|
+
aioLogger.debug(`api-mesh:get - ${JSON.stringify(meshConfig, null, 2)}`)
|
|
144
|
+
this.spinner.succeed('Got api-mesh config')
|
|
145
|
+
} else {
|
|
146
|
+
aioLogger.debug('api-mesh:get command was not found, meshConfig is not available for app:pack')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const deployJson = {
|
|
150
|
+
$schema: 'http://json-schema.org/draft-07/schema',
|
|
151
|
+
$id: 'https://adobe.io/schemas/app-builder-templates/1',
|
|
152
|
+
application,
|
|
153
|
+
extensions,
|
|
154
|
+
workspaces,
|
|
155
|
+
apis,
|
|
156
|
+
meshConfig,
|
|
157
|
+
runtime: true // always true for App Builder apps
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await writeFile(
|
|
161
|
+
path.join(DEFAULTS.ARTIFACTS_FOLDER, DEFAULTS.DEPLOY_YAML_FILE),
|
|
162
|
+
yaml.dump(deployJson),
|
|
163
|
+
{ overwrite: true })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Copies a list of files to a folder.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} destinationFolder the destination folder for the files
|
|
170
|
+
* @param {Array<string>} filesList a list of files to copy
|
|
171
|
+
*/
|
|
172
|
+
async copyPackageFiles (destinationFolder, filesList) {
|
|
173
|
+
for (const src of filesList) {
|
|
174
|
+
const dest = path.join(destinationFolder, src)
|
|
175
|
+
if (await fs.pathExists(src)) {
|
|
176
|
+
aioLogger.debug(`Copying ${src} to ${dest}`)
|
|
177
|
+
await fs.copy(src, dest)
|
|
178
|
+
} else {
|
|
179
|
+
aioLogger.debug(`Skipping copy for ${src} (path does not exist)`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Zip a file/folder using archiver
|
|
186
|
+
*
|
|
187
|
+
* @param {string} filePath path of file/folder to zip
|
|
188
|
+
* @param {string} out output path
|
|
189
|
+
* @param {boolean} pathInZip internal path in zip
|
|
190
|
+
* @returns {Promise} returns with a blank promise when done
|
|
191
|
+
*/
|
|
192
|
+
zipHelper (filePath, out, pathInZip = false) {
|
|
193
|
+
aioLogger.debug(`Creating zip of file/folder '${filePath}'`)
|
|
194
|
+
const stream = fs.createWriteStream(out)
|
|
195
|
+
const archive = archiver('zip', { zlib: { level: 9 } })
|
|
196
|
+
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
stream.on('close', () => resolve())
|
|
199
|
+
archive.pipe(stream)
|
|
200
|
+
archive.on('error', err => reject(err))
|
|
201
|
+
|
|
202
|
+
let stats
|
|
203
|
+
try {
|
|
204
|
+
stats = fs.lstatSync(filePath) // throws if enoent
|
|
205
|
+
} catch (e) {
|
|
206
|
+
archive.destroy()
|
|
207
|
+
reject(e)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (stats.isDirectory()) {
|
|
211
|
+
archive.directory(filePath, pathInZip)
|
|
212
|
+
} else { // if (stats.isFile()) {
|
|
213
|
+
archive.file(filePath, { name: pathInZip || path.basename(filePath) })
|
|
214
|
+
}
|
|
215
|
+
archive.finalize()
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Gets a list of files that are to be packed.
|
|
221
|
+
*
|
|
222
|
+
* This runs `npm pack` to get the list.
|
|
223
|
+
*
|
|
224
|
+
* @param {Array<string>} filesToExclude a list of files to exclude
|
|
225
|
+
* @param {string} workingDirectory the working directory to run `npm pack` in
|
|
226
|
+
* @returns {Array<string>} a list of files that are to be packed
|
|
227
|
+
*/
|
|
228
|
+
async filesToPack (filesToExclude = [], workingDirectory = process.cwd()) {
|
|
229
|
+
const { stdout } = await execa('npm', ['pack', '--dry-run', '--json'], { cwd: workingDirectory })
|
|
230
|
+
|
|
231
|
+
const { files } = JSON.parse(stdout)[0]
|
|
232
|
+
return files
|
|
233
|
+
.map(file => file.path)
|
|
234
|
+
.filter(file => !filesToExclude.includes(file))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* An annotation called code-download will be added to all actions in app.config.yaml
|
|
239
|
+
* (and linked yaml configs for example in extensions). This value will be set to false.
|
|
240
|
+
* The annotation will by default be true if not set.
|
|
241
|
+
*
|
|
242
|
+
* @param {object} appConfig the app's configuration file
|
|
243
|
+
*/
|
|
244
|
+
async addCodeDownloadAnnotation (appConfig) {
|
|
245
|
+
// get the configFiles that have runtime manifests
|
|
246
|
+
const configFiles = []
|
|
247
|
+
for (const [, value] of Object.entries(appConfig.includeIndex)) {
|
|
248
|
+
const { key } = value
|
|
249
|
+
if (key === 'runtimeManifest' || key === 'application.runtimeManifest') {
|
|
250
|
+
configFiles.push(value)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// for each configFile, we modify each action to have the "code-download: false" annotation
|
|
255
|
+
for (const configFile of configFiles) {
|
|
256
|
+
const configFilePath = path.join(DEFAULTS.ARTIFACTS_FOLDER, configFile.file)
|
|
257
|
+
const { values } = loadConfigFile(configFilePath)
|
|
258
|
+
|
|
259
|
+
const runtimeManifest = getObjectValue(values, configFile.key)
|
|
260
|
+
for (const [, pkgManifest] of Object.entries(runtimeManifest.packages)) {
|
|
261
|
+
// key is the package name (unused), value is the package manifest. we iterate through each package's "actions"
|
|
262
|
+
for (const [, actionManifest] of Object.entries(pkgManifest.actions)) {
|
|
263
|
+
// key is the action name (unused), value is the action manifest. we add the "code-download: false" annotation
|
|
264
|
+
actionManifest.annotations['code-download'] = false
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// write back the modified manifest to disk
|
|
269
|
+
await writeFile(configFilePath, yaml.dump(values), { overwrite: true })
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
Pack.hidden = true // hide from help for pre-release
|
|
275
|
+
|
|
276
|
+
Pack.description = chalk.yellow(`(Pre-release) This command will support packaging apps for redistribution.
|
|
277
|
+
`)
|
|
278
|
+
|
|
279
|
+
Pack.flags = {
|
|
280
|
+
...BaseCommand.flags,
|
|
281
|
+
output: Flags.string({
|
|
282
|
+
description: 'The packaged app output file path',
|
|
283
|
+
char: 'o',
|
|
284
|
+
default: DEFAULTS.OUTPUT_ZIP_FILE
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Pack.args = [
|
|
289
|
+
{
|
|
290
|
+
name: 'path',
|
|
291
|
+
description: 'Path to the app directory to package',
|
|
292
|
+
default: '.'
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
module.exports = Pack
|
package/src/commands/app/test.js
CHANGED
package/src/lib/app-helper.js
CHANGED
|
@@ -505,7 +505,32 @@ const createWebExportFilter = (filterValue) => {
|
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Get property from object with case insensitivity.
|
|
510
|
+
*
|
|
511
|
+
* @param {object} obj the object to wrap
|
|
512
|
+
* @param {string} key the key
|
|
513
|
+
* @private
|
|
514
|
+
*/
|
|
515
|
+
function getObjectProp (obj, key) {
|
|
516
|
+
return obj[Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase())]
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get a value in an object by dot notation.
|
|
521
|
+
*
|
|
522
|
+
* @param {object} obj the object to wrap
|
|
523
|
+
* @param {string} key the key
|
|
524
|
+
* @returns {object} the value
|
|
525
|
+
*/
|
|
526
|
+
function getObjectValue (obj, key) {
|
|
527
|
+
const keys = (key || '').toString().split('.')
|
|
528
|
+
return keys.filter(o => o.trim()).reduce((o, i) => o && getObjectProp(o, i), obj)
|
|
529
|
+
}
|
|
530
|
+
|
|
508
531
|
module.exports = {
|
|
532
|
+
getObjectValue,
|
|
533
|
+
getObjectProp,
|
|
509
534
|
createWebExportFilter,
|
|
510
535
|
isNpmInstalled,
|
|
511
536
|
isGitInstalled,
|
package/src/lib/defaults.js
CHANGED
|
@@ -30,7 +30,9 @@ module.exports = {
|
|
|
30
30
|
defaultHttpServerPort: 9080,
|
|
31
31
|
AIO_CONFIG_WORKSPACE_SERVICES: 'project.workspace.details.services',
|
|
32
32
|
AIO_CONFIG_ORG_SERVICES: 'project.org.details.services',
|
|
33
|
+
IMPORT_CONFIG_FILE: 'config.json',
|
|
33
34
|
USER_CONFIG_FILE: 'app.config.yaml',
|
|
35
|
+
DEPLOY_CONFIG_FILE: 'deploy.yaml',
|
|
34
36
|
LEGACY_RUNTIME_MANIFEST: 'manifest.yml',
|
|
35
37
|
INCLUDE_DIRECTIVE: '$include',
|
|
36
38
|
APPLICATION_CONFIG_KEY: 'application',
|
|
@@ -34,11 +34,16 @@ module.exports = async (config, isLocalDev = false, log = () => {}, filter = fal
|
|
|
34
34
|
}
|
|
35
35
|
if (inprocHook) {
|
|
36
36
|
const hookFilterEntities = Array.isArray(filter) ? filter : []
|
|
37
|
-
await inprocHook('deploy-actions', {
|
|
37
|
+
const hookResults = await inprocHook('deploy-actions', {
|
|
38
38
|
appConfig: config,
|
|
39
39
|
filterEntities: hookFilterEntities,
|
|
40
40
|
isLocalDev
|
|
41
41
|
})
|
|
42
|
+
if (hookResults?.failures?.length > 0) {
|
|
43
|
+
// output should be "Error : <plugin-name> : <error-message>\n" for each failure
|
|
44
|
+
log('Error: ' + hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '))
|
|
45
|
+
throw new Error(`Hook 'deploy-actions' failed with ${hookResults.failures[0].error}`)
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
const entities = await deployActions(config, deployConfig, log)
|
|
44
49
|
if (entities.actions) {
|
package/src/lib/import-helper.js
CHANGED
|
@@ -17,8 +17,8 @@ const fs = require('fs-extra')
|
|
|
17
17
|
const inquirer = require('inquirer')
|
|
18
18
|
const yaml = require('js-yaml')
|
|
19
19
|
const hjson = require('hjson')
|
|
20
|
-
const Ajv = require('ajv')
|
|
21
20
|
const { EOL } = require('os')
|
|
21
|
+
const { validateJsonWithSchema } = require('./install-helper')
|
|
22
22
|
|
|
23
23
|
const AIO_FILE = '.aio'
|
|
24
24
|
const ENV_FILE = '.env'
|
|
@@ -33,21 +33,6 @@ const CONSOLE_CONFIG_KEY = 'console'
|
|
|
33
33
|
// this into an init/constructor as it might create mocking issues in jest
|
|
34
34
|
const prompt = inquirer.createPromptModule({ output: process.stderr })
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Validate the config json
|
|
38
|
-
*
|
|
39
|
-
* @param {object} configJson the json to validate
|
|
40
|
-
* @returns {object} with keys valid (boolean) and errors (object). errors is null if no errors
|
|
41
|
-
*/
|
|
42
|
-
function validateConfig (configJson) {
|
|
43
|
-
/* eslint-disable-next-line node/no-unpublished-require */
|
|
44
|
-
const schema = require('../../schema/config.schema.json')
|
|
45
|
-
const ajv = new Ajv({ allErrors: true })
|
|
46
|
-
const validate = ajv.compile(schema)
|
|
47
|
-
|
|
48
|
-
return { valid: validate(configJson), errors: validate.errors }
|
|
49
|
-
}
|
|
50
|
-
|
|
51
36
|
/**
|
|
52
37
|
* Load a config file
|
|
53
38
|
*
|
|
@@ -92,7 +77,7 @@ function loadConfigFile (fileOrBuffer) {
|
|
|
92
77
|
*/
|
|
93
78
|
function loadAndValidateConfigFile (fileOrBuffer) {
|
|
94
79
|
const res = loadConfigFile(fileOrBuffer)
|
|
95
|
-
const { valid: configIsValid, errors: configErrors } =
|
|
80
|
+
const { valid: configIsValid, errors: configErrors } = validateJsonWithSchema(res.values, 'config.json')
|
|
96
81
|
if (!configIsValid) {
|
|
97
82
|
const message = `Missing or invalid keys in config: ${JSON.stringify(configErrors, null, 2)}`
|
|
98
83
|
throw new Error(message)
|
|
@@ -616,7 +601,7 @@ async function importConfigJson (configFileOrBuffer, destinationFolder = process
|
|
|
616
601
|
}
|
|
617
602
|
|
|
618
603
|
module.exports = {
|
|
619
|
-
|
|
604
|
+
writeFile,
|
|
620
605
|
loadConfigFile,
|
|
621
606
|
loadAndValidateConfigFile,
|
|
622
607
|
writeConsoleConfig,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2023 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
7
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
8
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
9
|
+
governing permissions and limitations under the License.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const Ajv = require('ajv')
|
|
13
|
+
const ajvAddFormats = require('ajv-formats')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validate the file json with one of our schemas.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} fileJson the json to validate
|
|
19
|
+
* @param {string} schemaName one of config.json, app.config.yaml, deploy.yaml
|
|
20
|
+
* @returns {object} with keys valid (boolean) and errors (object). errors is null if no errors
|
|
21
|
+
*/
|
|
22
|
+
function validateJsonWithSchema (fileJson, schemaName) {
|
|
23
|
+
/* eslint-disable-next-line node/no-unpublished-require */
|
|
24
|
+
const schemas = require('../../schema/index')
|
|
25
|
+
const ajv = new Ajv({
|
|
26
|
+
allErrors: true,
|
|
27
|
+
allowUnionTypes: true
|
|
28
|
+
})
|
|
29
|
+
ajvAddFormats(ajv)
|
|
30
|
+
|
|
31
|
+
const validate = ajv.compile(schemas[schemaName])
|
|
32
|
+
return { valid: validate(fileJson), errors: validate.errors }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
validateJsonWithSchema
|
|
37
|
+
}
|