forceios 13.0.2 → 13.2.0-alpha.0
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 +27 -7
- package/package.json +1 -1
- package/shared/configHelper.js +115 -50
- package/shared/constants.js +118 -17
- package/shared/createHelper.js +172 -40
- package/shared/jsonChecker.js +2 -1
- package/shared/oclifAdapter.js +87 -18
- package/shared/templateHelper.js +272 -6
package/README.md
CHANGED
|
@@ -39,21 +39,46 @@ forceios create
|
|
|
39
39
|
--packagename=app package identifier (e.g. com.mycompany.myapp)
|
|
40
40
|
--organization=organization name (your company's/organization's name)
|
|
41
41
|
[--outputdir=output directory (leave empty for current directory)]
|
|
42
|
+
[--consumerkey=OAuth consumer key for the Salesforce External Client App or Connected App]
|
|
43
|
+
[--callbackurl=OAuth callback URL for the Salesforce External Client App or Connected App]
|
|
44
|
+
[--loginserver=Login server URL for the Salesforce org]
|
|
42
45
|
|
|
43
46
|
OR
|
|
44
47
|
|
|
45
48
|
# create an iOS native mobile application from a template
|
|
46
49
|
forceios createwithtemplate
|
|
47
|
-
--
|
|
50
|
+
[--templatesource=git repo URL (optionally with #branch) or local path to a templates suite (root must contain templates.json)]
|
|
51
|
+
[--templaterepouri=template repo URI or Mobile SDK template name]
|
|
52
|
+
[--template=template name within the templates suite (e.g. ReactNativeTemplate)]
|
|
48
53
|
--appname=application name
|
|
49
54
|
--packagename=app package identifier (e.g. com.mycompany.myapp)
|
|
50
55
|
--organization=organization name (your company's/organization's name)
|
|
51
56
|
[--outputdir=output directory (leave empty for current directory)]
|
|
57
|
+
[--consumerkey=OAuth consumer key for the Salesforce External Client App or Connected App]
|
|
58
|
+
[--callbackurl=OAuth callback URL for the Salesforce External Client App or Connected App]
|
|
59
|
+
[--loginserver=Login server URL for the Salesforce org]
|
|
52
60
|
|
|
53
61
|
OR
|
|
54
62
|
|
|
55
|
-
#
|
|
63
|
+
# show version of Mobile SDK
|
|
64
|
+
forceios version
|
|
65
|
+
|
|
66
|
+
OR
|
|
67
|
+
|
|
68
|
+
# list available Mobile SDK templates to create an iOS native mobile application
|
|
56
69
|
forceios listtemplates
|
|
70
|
+
[--templatesource=git repo URL (optionally with #branch) or local path to a templates suite (root must contain templates.json)]
|
|
71
|
+
[--doc=include verbose documentation from template.json files]
|
|
72
|
+
[--json=output response in JSON format]
|
|
73
|
+
|
|
74
|
+
OR
|
|
75
|
+
|
|
76
|
+
# list details for a specific Mobile SDK template to create an iOS native mobile application
|
|
77
|
+
forceios describetemplate
|
|
78
|
+
[--templatesource=git repo URL (optionally with #branch) or local path to a templates suite (root must contain templates.json)]
|
|
79
|
+
[--template=template name within the templates suite (e.g. ReactNativeTemplate)]
|
|
80
|
+
[--doc=include verbose documentation from template.json files]
|
|
81
|
+
[--json=output response in JSON format]
|
|
57
82
|
|
|
58
83
|
OR
|
|
59
84
|
|
|
@@ -64,11 +89,6 @@ forceios checkconfig
|
|
|
64
89
|
|
|
65
90
|
OR
|
|
66
91
|
|
|
67
|
-
# show version of Mobile SDK
|
|
68
|
-
forceios version
|
|
69
|
-
|
|
70
|
-
OR
|
|
71
|
-
|
|
72
92
|
forceios
|
|
73
93
|
```
|
|
74
94
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forceios",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.2.0-alpha.0",
|
|
4
4
|
"description": "Utilities for creating mobile apps based on the Salesforce Mobile SDK for iOS",
|
|
5
5
|
"keywords": [ "mobilesdk", "ios", "salesforce", "mobile", "sdk" ],
|
|
6
6
|
"homepage": "https://github.com/forcedotcom/SalesforceMobileSDK-iOS",
|
package/shared/configHelper.js
CHANGED
|
@@ -32,33 +32,37 @@ var path = require('path'),
|
|
|
32
32
|
COLOR = require('./outputColors'),
|
|
33
33
|
commandLineUtils = require('./commandLineUtils'),
|
|
34
34
|
logInfo = require('./utils').logInfo,
|
|
35
|
+
separateRepoUrlPathBranch = require('./utils').separateRepoUrlPathBranch,
|
|
35
36
|
getTemplates = require('./templateHelper').getTemplates,
|
|
37
|
+
getTemplate = require('./templateHelper').getTemplate,
|
|
38
|
+
displayTemplateList = require('./templateHelper').displayTemplateList,
|
|
39
|
+
displayTemplateDetail = require('./templateHelper').displayTemplateDetail,
|
|
36
40
|
validateJson = require('./jsonChecker').validateJson;
|
|
37
41
|
|
|
38
42
|
function applyCli(f, cli) {
|
|
39
|
-
return typeof f === 'function' ? f(cli): f;
|
|
43
|
+
return typeof f === 'function' ? f(cli) : f;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
function getArgsExpanded(cli, commandName) {
|
|
43
47
|
var argNames = applyCli(SDK.commands[commandName].args, cli);
|
|
44
48
|
return argNames
|
|
45
49
|
.map(argName => SDK.args[argName])
|
|
46
|
-
.map(arg =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
.map(arg =>
|
|
51
|
+
({
|
|
52
|
+
name: arg.name,
|
|
53
|
+
'char': arg.char,
|
|
54
|
+
description: applyCli(arg.description, cli),
|
|
55
|
+
longDescription: applyCli(arg.longDescription, cli),
|
|
56
|
+
prompt: applyCli(arg.prompt, cli),
|
|
57
|
+
error: applyCli(arg.error, cli),
|
|
58
|
+
validate: applyCli(arg.validate, cli),
|
|
59
|
+
promptIf: arg.promptIf,
|
|
60
|
+
required: arg.required === undefined ? true : arg.required,
|
|
61
|
+
hasValue: arg.hasValue === undefined ? true : arg.hasValue,
|
|
62
|
+
hidden: applyCli(arg.hidden, cli),
|
|
63
|
+
type: arg.type
|
|
64
|
+
})
|
|
65
|
+
);
|
|
62
66
|
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -69,7 +73,8 @@ function getCommandExpanded(cli, commandName) {
|
|
|
69
73
|
args: getArgsExpanded(cli, commandName),
|
|
70
74
|
description: applyCli(command.description, cli),
|
|
71
75
|
longDescription: applyCli(command.longDescription, cli),
|
|
72
|
-
help: applyCli(command.help, cli)
|
|
76
|
+
help: applyCli(command.help, cli),
|
|
77
|
+
supportCustomFlags: command.supportCustomFlags
|
|
73
78
|
};
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -81,28 +86,32 @@ function readConfig(args, cli, handler) {
|
|
|
81
86
|
var processorList = null;
|
|
82
87
|
|
|
83
88
|
switch (commandName || '') {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
89
|
+
case SDK.commands.version.name:
|
|
90
|
+
printVersion(cli);
|
|
91
|
+
process.exit(0);
|
|
92
|
+
break;
|
|
93
|
+
case SDK.commands.create.name:
|
|
94
|
+
case SDK.commands.createwithtemplate.name:
|
|
95
|
+
processorList = buildArgsProcessorList(cli, commandName);
|
|
96
|
+
commandLineUtils.processArgsInteractive(commandLineArgs, processorList, handler);
|
|
97
|
+
break;
|
|
98
|
+
case SDK.commands.checkconfig.name:
|
|
99
|
+
processorList = buildArgsProcessorList(cli, commandName);
|
|
100
|
+
commandLineUtils.processArgsInteractive(commandLineArgs, processorList, function (config) {
|
|
101
|
+
validateJson(config.configpath, config.configtype);
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
case SDK.commands.listtemplates.name:
|
|
105
|
+
listTemplates(cli, commandLineArgs);
|
|
106
|
+
process.exit(0);
|
|
107
|
+
break;
|
|
108
|
+
case SDK.commands.describetemplate.name:
|
|
109
|
+
describeTemplate(cli, commandLineArgs);
|
|
110
|
+
process.exit(0);
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
usage(cli);
|
|
114
|
+
process.exit(1);
|
|
106
115
|
};
|
|
107
116
|
|
|
108
117
|
|
|
@@ -115,20 +124,76 @@ function printVersion(cli) {
|
|
|
115
124
|
function printArgs(cli, commandName) {
|
|
116
125
|
getArgsExpanded(cli, commandName)
|
|
117
126
|
.filter(arg => !arg.hidden)
|
|
118
|
-
.forEach(arg => logInfo(' ' + (!arg.required
|
|
127
|
+
.forEach(arg => logInfo(' ' + (!arg.required ? '[' : '') + '--' + arg.name + '=' + arg.description + (!arg.required ? ']' : ''), COLOR.magenta));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function listTemplates(cli, commandLineArgs) {
|
|
131
|
+
var cliName = cli.name;
|
|
132
|
+
|
|
133
|
+
// Parse command line arguments to extract templatesource or templaterepouri
|
|
134
|
+
var templateSource = null;
|
|
135
|
+
var templateRepoUri = null;
|
|
136
|
+
var includeDescriptions = false;
|
|
137
|
+
var outputJson = false;
|
|
138
|
+
if (commandLineArgs && commandLineArgs.length > 0) {
|
|
139
|
+
try {
|
|
140
|
+
var argsMap = commandLineUtils.parseArgs(commandLineArgs);
|
|
141
|
+
templateSource = argsMap[SDK.args.templateSource.name];
|
|
142
|
+
templateRepoUri = argsMap[SDK.args.templateRepoUri.name];
|
|
143
|
+
includeDescriptions = argsMap.hasOwnProperty(SDK.args.doc.name);
|
|
144
|
+
outputJson = argsMap.hasOwnProperty(SDK.args.json.name);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// If argument parsing fails, continue without templateRepoUri
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
var source = templateSource || templateRepoUri;
|
|
151
|
+
var applicableTemplates = getTemplates(cli, source, includeDescriptions);
|
|
152
|
+
|
|
153
|
+
// Use shared display function
|
|
154
|
+
var commandPrefix = cliName + ' ' + SDK.commands.createwithtemplate.name;
|
|
155
|
+
displayTemplateList(applicableTemplates, source, cliName, commandPrefix, includeDescriptions, null, outputJson);
|
|
119
156
|
}
|
|
120
157
|
|
|
121
|
-
function
|
|
158
|
+
function describeTemplate(cli, commandLineArgs) {
|
|
122
159
|
var cliName = cli.name;
|
|
123
|
-
var applicableTemplates = getTemplates(cli);
|
|
124
160
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
// Parse command line arguments to extract templatesource, template, and doc
|
|
162
|
+
var templateSource = null;
|
|
163
|
+
var templateRepoUri = null;
|
|
164
|
+
var templateName = null;
|
|
165
|
+
var includeDescriptions = false;
|
|
166
|
+
var outputJson = false;
|
|
167
|
+
if (commandLineArgs && commandLineArgs.length > 0) {
|
|
168
|
+
try {
|
|
169
|
+
var argsMap = commandLineUtils.parseArgs(commandLineArgs);
|
|
170
|
+
templateSource = argsMap[SDK.args.templateSource.name];
|
|
171
|
+
templateRepoUri = argsMap[SDK.args.templateRepoUri.name];
|
|
172
|
+
templateName = argsMap[SDK.args.template.name];
|
|
173
|
+
includeDescriptions = argsMap.hasOwnProperty(SDK.args.doc.name);
|
|
174
|
+
outputJson = argsMap.hasOwnProperty(SDK.args.json.name);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
// If argument parsing fails, continue without templateRepoUri
|
|
177
|
+
}
|
|
130
178
|
}
|
|
131
|
-
|
|
179
|
+
|
|
180
|
+
// Check if template name is provided
|
|
181
|
+
if (!templateName) {
|
|
182
|
+
logInfo('Error: Template name is required. Use --template to specify the template name.', COLOR.red);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
var source = templateSource || templateRepoUri;
|
|
187
|
+
var template = getTemplate(templateName, source, includeDescriptions);
|
|
188
|
+
|
|
189
|
+
if (!template) {
|
|
190
|
+
logInfo('Error: Template "' + templateName + '" not found.', COLOR.red);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Use shared display function
|
|
195
|
+
var commandPrefix = cliName + ' ' + SDK.commands.createwithtemplate.name;
|
|
196
|
+
displayTemplateDetail(template, source, cliName, commandPrefix, includeDescriptions, null, outputJson);
|
|
132
197
|
}
|
|
133
198
|
|
|
134
199
|
function usage(cli) {
|
|
@@ -184,7 +249,7 @@ function buildArgsProcessorList(cli, commandName) {
|
|
|
184
249
|
// * preprocessor: function or null
|
|
185
250
|
//
|
|
186
251
|
function addProcessorFor(argProcessorList, argName, prompt, error, validation, preprocessor) {
|
|
187
|
-
argProcessorList.addArgProcessor(argName, prompt, function(val) {
|
|
252
|
+
argProcessorList.addArgProcessor(argName, prompt, function (val) {
|
|
188
253
|
val = val.trim();
|
|
189
254
|
|
|
190
255
|
// validation is either a function or null
|
package/shared/constants.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
var path = require('path'),
|
|
29
29
|
shelljs = require('shelljs');
|
|
30
30
|
|
|
31
|
-
var VERSION= '13.0.
|
|
31
|
+
var VERSION= '13.2.0-alpha.0';
|
|
32
32
|
|
|
33
33
|
module.exports = {
|
|
34
34
|
version: VERSION,
|
|
@@ -60,12 +60,12 @@ module.exports = {
|
|
|
60
60
|
},
|
|
61
61
|
cordova: {
|
|
62
62
|
checkCmd: 'cordova -v',
|
|
63
|
-
|
|
63
|
+
pluginRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-CordovaPlugin#dev', // dev
|
|
64
64
|
minVersion: '12.0.0',
|
|
65
|
-
pluginRepoUri: 'salesforce-mobilesdk-cordova-plugin@v' + VERSION, // GA
|
|
65
|
+
// pluginRepoUri: 'salesforce-mobilesdk-cordova-plugin@v' + VERSION, // GA
|
|
66
66
|
platformVersion: {
|
|
67
67
|
ios: '7.1.1',
|
|
68
|
-
android: '
|
|
68
|
+
android: '14.0.1'
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
sf: {
|
|
@@ -79,8 +79,8 @@ module.exports = {
|
|
|
79
79
|
android: 'Android Studio'
|
|
80
80
|
},
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
templatesRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-Templates#v' + VERSION, // GA
|
|
82
|
+
templatesRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-Templates#dev', // dev
|
|
83
|
+
// templatesRepoUri: 'https://github.com/forcedotcom/SalesforceMobileSDK-Templates#v' + VERSION, // GA
|
|
84
84
|
|
|
85
85
|
forceclis: {
|
|
86
86
|
forceios: {
|
|
@@ -95,7 +95,7 @@ module.exports = {
|
|
|
95
95
|
'native': 'iOSNativeTemplate',
|
|
96
96
|
'native_swift': 'iOSNativeSwiftTemplate'
|
|
97
97
|
},
|
|
98
|
-
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig']
|
|
98
|
+
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'describetemplate', 'checkconfig']
|
|
99
99
|
},
|
|
100
100
|
forcedroid: {
|
|
101
101
|
name: 'forcedroid',
|
|
@@ -109,7 +109,7 @@ module.exports = {
|
|
|
109
109
|
'native': 'AndroidNativeTemplate',
|
|
110
110
|
'native_kotlin': 'AndroidNativeKotlinTemplate'
|
|
111
111
|
},
|
|
112
|
-
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig']
|
|
112
|
+
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'describetemplate', 'checkconfig']
|
|
113
113
|
},
|
|
114
114
|
forcehybrid: {
|
|
115
115
|
name: 'forcehybrid',
|
|
@@ -123,7 +123,7 @@ module.exports = {
|
|
|
123
123
|
'hybrid_local': 'HybridLocalTemplate',
|
|
124
124
|
'hybrid_remote': 'HybridRemoteTemplate'
|
|
125
125
|
},
|
|
126
|
-
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig']
|
|
126
|
+
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'describetemplate', 'checkconfig']
|
|
127
127
|
},
|
|
128
128
|
forcereact: {
|
|
129
129
|
name: 'forcereact',
|
|
@@ -137,10 +137,10 @@ module.exports = {
|
|
|
137
137
|
'react_native': 'ReactNativeTemplate',
|
|
138
138
|
'react_native_typescript': 'ReactNativeTypeScriptTemplate'
|
|
139
139
|
},
|
|
140
|
-
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'checkconfig']
|
|
140
|
+
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'describetemplate', 'checkconfig']
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
args: {
|
|
145
145
|
platform: {
|
|
146
146
|
name: 'platform',
|
|
@@ -171,6 +171,34 @@ module.exports = {
|
|
|
171
171
|
prompt: 'Enter URI of repo containing template application or a Mobile SDK template name:',
|
|
172
172
|
error: cli => val => 'Invalid value for template repo uri: \'' + val + '\'.',
|
|
173
173
|
validate: cli => val => /^\S+$/.test(val),
|
|
174
|
+
promptIf: otherArgs => !otherArgs.templatesource,
|
|
175
|
+
required: false,
|
|
176
|
+
type: 'string'
|
|
177
|
+
},
|
|
178
|
+
templateSource: {
|
|
179
|
+
name: 'templatesource',
|
|
180
|
+
'char': 'S',
|
|
181
|
+
description: 'git repo URL (optionally with #branch) or local path to a templates suite (root must contain templates.json)',
|
|
182
|
+
longDescription: 'Location of a suite of templates. Can be a git URL with optional #branch suffix or a local filesystem path whose root contains templates.json.',
|
|
183
|
+
prompt: 'Enter git URL or local path to your templates suite:',
|
|
184
|
+
error: cli => val => 'Invalid value for template source: \'' + val + '\'.',
|
|
185
|
+
validate: cli => val => /\S+/.test(val),
|
|
186
|
+
// Process only when explicitly provided to avoid prompting during interactive flows
|
|
187
|
+
promptIf: otherArgs => typeof otherArgs.templatesource !== 'undefined',
|
|
188
|
+
required: false,
|
|
189
|
+
type: 'string'
|
|
190
|
+
},
|
|
191
|
+
template: {
|
|
192
|
+
name: 'template',
|
|
193
|
+
'char': 'm',
|
|
194
|
+
description: 'template name within the templates suite (e.g. ReactNativeTemplate)',
|
|
195
|
+
longDescription: 'Name of the template to use from the suite specified by --templatesource. Should match the directory name or the path field in templates.json.',
|
|
196
|
+
prompt: 'Enter the template name from your template source:',
|
|
197
|
+
error: cli => val => 'Invalid value for template: \'' + val + '\'.',
|
|
198
|
+
validate: cli => val => /\S+/.test(val),
|
|
199
|
+
// Only prompt for template when a templatesource is provided
|
|
200
|
+
promptIf: otherArgs => !!otherArgs.templatesource,
|
|
201
|
+
required: false,
|
|
174
202
|
type: 'string'
|
|
175
203
|
},
|
|
176
204
|
appName: {
|
|
@@ -268,7 +296,7 @@ module.exports = {
|
|
|
268
296
|
type: 'string',
|
|
269
297
|
hidden: true
|
|
270
298
|
},
|
|
271
|
-
|
|
299
|
+
sdkDependencies: {
|
|
272
300
|
name: 'sdkdependencies',
|
|
273
301
|
description: 'override sdk dependencies',
|
|
274
302
|
'char': 'd',
|
|
@@ -277,7 +305,64 @@ module.exports = {
|
|
|
277
305
|
required: false,
|
|
278
306
|
type: 'string',
|
|
279
307
|
hidden: true
|
|
280
|
-
|
|
308
|
+
},
|
|
309
|
+
doc: {
|
|
310
|
+
name: 'doc',
|
|
311
|
+
'char': 'D',
|
|
312
|
+
description: 'include verbose documentation from template.json files',
|
|
313
|
+
longDescription: 'When specified, includes detailed metadata from each template\'s template.json file if available (displayName, description, useCase, features, complexity).',
|
|
314
|
+
prompt: null,
|
|
315
|
+
error: cli => val => 'Invalid value for doc flag: \'' + val + '\'.',
|
|
316
|
+
validate: cli => val => true, // Boolean flag, no validation needed
|
|
317
|
+
required: false,
|
|
318
|
+
type: 'boolean',
|
|
319
|
+
hidden: false
|
|
320
|
+
},
|
|
321
|
+
json: {
|
|
322
|
+
name: 'json',
|
|
323
|
+
'char': 'j',
|
|
324
|
+
description: 'output response in JSON format',
|
|
325
|
+
longDescription: 'When specified, outputs the response in JSON format instead of human-readable text. Useful for programmatic consumption.',
|
|
326
|
+
prompt: null,
|
|
327
|
+
error: cli => val => 'Invalid value for json flag: \'' + val + '\'.',
|
|
328
|
+
validate: cli => val => true, // Boolean flag, no validation needed
|
|
329
|
+
required: false,
|
|
330
|
+
type: 'boolean',
|
|
331
|
+
hidden: false
|
|
332
|
+
},
|
|
333
|
+
consumerKey: {
|
|
334
|
+
name: 'consumerkey',
|
|
335
|
+
'char': 'c',
|
|
336
|
+
description: 'OAuth consumer key for the Salesforce External Client App or Connected App',
|
|
337
|
+
longDescription: 'The OAuth consumer key (client ID) for your Salesforce External Client App or Connected App. When provided, this will be automatically configured in the generated app.',
|
|
338
|
+
prompt: 'Enter your OAuth consumer key (leave empty to manually configure this value in the project\'s bootconfig file):',
|
|
339
|
+
error: cli => val => 'Invalid value for consumer key: \'' + val + '\'.',
|
|
340
|
+
validate: cli => val => val === undefined || val === '' || /\S+/.test(val),
|
|
341
|
+
required: false,
|
|
342
|
+
type: 'string'
|
|
343
|
+
},
|
|
344
|
+
callbackURL: {
|
|
345
|
+
name: 'callbackurl',
|
|
346
|
+
'char': 'u',
|
|
347
|
+
description: 'OAuth callback URL for the Salesforce External Client App or Connected App',
|
|
348
|
+
longDescription: 'The OAuth callback URL (redirect URI) for your Salesforce External Client App or Connected App. When provided, this will be automatically configured in the generated app.',
|
|
349
|
+
prompt: 'Enter your OAuth callback URL (leave empty to manually configure this value in the project\'s bootconfig file):',
|
|
350
|
+
error: cli => val => 'Invalid value for callback URL: \'' + val + '\'.',
|
|
351
|
+
validate: cli => val => val === undefined || val === '' || /\S+/.test(val),
|
|
352
|
+
required: false,
|
|
353
|
+
type: 'string'
|
|
354
|
+
},
|
|
355
|
+
loginServer: {
|
|
356
|
+
name: 'loginserver',
|
|
357
|
+
'char': 'l',
|
|
358
|
+
description: 'Login server URL for the Salesforce org',
|
|
359
|
+
longDescription: 'The login server URL for your Salesforce org (e.g. https://login.salesforce.com, https://test.salesforce.com, or custom domain). When provided, this will be automatically configured in the generated app.',
|
|
360
|
+
prompt: 'Enter your login server URL (leave empty for https://login.salesforce.com):',
|
|
361
|
+
error: cli => val => 'Invalid value for login server: \'' + val + '\'.',
|
|
362
|
+
validate: cli => val => val === undefined || val === '' || /\S+/.test(val),
|
|
363
|
+
required: false,
|
|
364
|
+
type: 'string'
|
|
365
|
+
}
|
|
281
366
|
},
|
|
282
367
|
|
|
283
368
|
commands: {
|
|
@@ -290,6 +375,9 @@ module.exports = {
|
|
|
290
375
|
'organization',
|
|
291
376
|
cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null,
|
|
292
377
|
'outputDir',
|
|
378
|
+
'consumerKey',
|
|
379
|
+
'callbackURL',
|
|
380
|
+
'loginServer',
|
|
293
381
|
'verbose',
|
|
294
382
|
cli.name === 'forcehybrid' ? 'pluginRepoUri' : null,
|
|
295
383
|
'sdkDependencies'
|
|
@@ -301,19 +389,25 @@ module.exports = {
|
|
|
301
389
|
createwithtemplate: {
|
|
302
390
|
name: 'createwithtemplate',
|
|
303
391
|
args: cli => [cli.platforms.length > 1 ? 'platform' : null,
|
|
392
|
+
'templateSource',
|
|
304
393
|
'templateRepoUri',
|
|
394
|
+
'template',
|
|
305
395
|
'appName',
|
|
306
396
|
'packageName',
|
|
307
397
|
'organization',
|
|
308
398
|
cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null,
|
|
309
399
|
'outputDir',
|
|
400
|
+
'consumerKey',
|
|
401
|
+
'callbackURL',
|
|
402
|
+
'loginServer',
|
|
310
403
|
'verbose',
|
|
311
404
|
cli.name === 'forcehybrid' ? 'pluginRepoUri' : null,
|
|
312
|
-
|
|
405
|
+
'sdkDependencies'
|
|
313
406
|
].filter(x=>x!=null),
|
|
314
407
|
description: cli => 'create ' + cli.purpose + ' from a template',
|
|
315
408
|
longDescription: cli => 'Create ' + cli.purpose + ' from a template.',
|
|
316
|
-
help: 'This command initiates creation of a new app based on the Mobile SDK template that you specify. The template can be a specialized app for your app type that Mobile SDK provides, or your own custom app that you\'ve configured to use as a template. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/ios_new_project_template.htm for information on custom templates.'
|
|
409
|
+
help: 'This command initiates creation of a new app based on the Mobile SDK template that you specify. The template can be a specialized app for your app type that Mobile SDK provides, or your own custom app that you\'ve configured to use as a template. See https://developer.salesforce.com/docs/atlas.en-us.mobile_sdk.meta/mobile_sdk/ios_new_project_template.htm for information on custom templates.',
|
|
410
|
+
supportCustomFlags: true
|
|
317
411
|
},
|
|
318
412
|
version: {
|
|
319
413
|
name: 'version',
|
|
@@ -324,10 +418,17 @@ module.exports = {
|
|
|
324
418
|
},
|
|
325
419
|
listtemplates: {
|
|
326
420
|
name: 'listtemplates',
|
|
327
|
-
args: [],
|
|
421
|
+
args: ['templateSource', 'doc', 'json'],
|
|
328
422
|
description: cli => 'list available Mobile SDK templates to create ' + cli.purpose,
|
|
329
423
|
longDescription: cli => 'List available Mobile SDK templates to create ' + cli.purpose + '.',
|
|
330
|
-
help: 'This command displays the list of available Mobile SDK templates. You can copy repo paths from the output for use with the createwithtemplate command.'
|
|
424
|
+
help: 'This command displays the list of available Mobile SDK templates. You can copy repo paths from the output for use with the createwithtemplate command. Use --templatesource to specify a custom template repository or leave blank to use the default template repository. Use --doc to include detailed metadata from template.json files (displayName, description, useCase, features, complexity). Use --json to output the response in JSON format.'
|
|
425
|
+
},
|
|
426
|
+
describetemplate: {
|
|
427
|
+
name: 'describetemplate',
|
|
428
|
+
args: ['templateSource', 'template', 'doc', 'json'],
|
|
429
|
+
description: cli => 'list details for a specific Mobile SDK template to create ' + cli.purpose,
|
|
430
|
+
longDescription: cli => 'List details for a specific Mobile SDK template to create ' + cli.purpose + '.',
|
|
431
|
+
help: 'This command displays detailed information about a specific Mobile SDK template. Use --templatesource to specify a custom template repository or leave blank to use the default template repository. Use --template to specify the template name. Use --doc to include verbose metadata from template.json files. Use --json to output the response in JSON format.'
|
|
331
432
|
},
|
|
332
433
|
checkconfig: {
|
|
333
434
|
name: 'checkconfig',
|
package/shared/createHelper.js
CHANGED
|
@@ -32,7 +32,11 @@ var path = require('path'),
|
|
|
32
32
|
configHelper = require('./configHelper'),
|
|
33
33
|
prepareTemplate = require('./templateHelper').prepareTemplate,
|
|
34
34
|
getSDKTemplateURI = require('./templateHelper').getSDKTemplateURI,
|
|
35
|
-
fs = require('fs')
|
|
35
|
+
fs = require('fs'),
|
|
36
|
+
Ajv = require('ajv'),
|
|
37
|
+
COLOR = require('./outputColors'),
|
|
38
|
+
readJsonFile = require('./jsonChecker').readJsonFile,
|
|
39
|
+
JSON5 = require('json5');
|
|
36
40
|
|
|
37
41
|
// Constant
|
|
38
42
|
var SERVER_PROJECT_DIR = 'server';
|
|
@@ -109,85 +113,150 @@ function createHybridApp(config) {
|
|
|
109
113
|
// Run cordova prepare
|
|
110
114
|
utils.runProcessThrowError('cordova prepare', config.projectDir);
|
|
111
115
|
|
|
116
|
+
// Add theme for Android API 35
|
|
117
|
+
if (config.platform.split(',').includes('android')) {
|
|
118
|
+
createAndroidAPI35Theme(config.projectDir);
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
// Done
|
|
113
122
|
return prepareResult;
|
|
114
123
|
}
|
|
115
124
|
|
|
125
|
+
//
|
|
126
|
+
// Add Android API 35 theme file
|
|
127
|
+
//
|
|
128
|
+
function createAndroidAPI35Theme(projectDir) {
|
|
129
|
+
const dirPath = path.join(projectDir, 'platforms', 'android', 'app', 'src', 'main', 'res', 'values-v35');
|
|
130
|
+
const filePath = path.join(dirPath, 'themes.xml');
|
|
131
|
+
const fileContents = `<?xml version='1.0' encoding='utf-8'?>
|
|
132
|
+
<resources>
|
|
133
|
+
<!-- Override for API 35+ to fix white status bar with white icons issue -->
|
|
134
|
+
<style name="SalesforceSDK_SplashScreen" parent="Theme.SplashScreen.IconBackground">
|
|
135
|
+
<item name="postSplashScreenTheme">@style/Theme.AppCompat.NoActionBar</item>
|
|
136
|
+
<!-- Use dark icons on light status bar background -->
|
|
137
|
+
<item name="android:windowLightStatusBar">true</item>
|
|
138
|
+
</style>
|
|
139
|
+
</resources>`
|
|
140
|
+
|
|
141
|
+
// Ensure the directory exists
|
|
142
|
+
utils.mkDirIfNeeded(dirPath);
|
|
143
|
+
|
|
144
|
+
// Write the file
|
|
145
|
+
fs.writeFileSync(filePath, fileContents, 'utf8');
|
|
146
|
+
}
|
|
147
|
+
|
|
116
148
|
//
|
|
117
149
|
// Print details
|
|
118
150
|
//
|
|
119
151
|
function printDetails(config) {
|
|
120
152
|
// Printing out details
|
|
121
153
|
var details = ['Creating ' + config.platform.replace(',', ' and ') + ' ' + config.apptype + ' application using Salesforce Mobile SDK',
|
|
122
|
-
' with app name:
|
|
123
|
-
' package name:
|
|
124
|
-
' organization:
|
|
154
|
+
' with app name: ' + config.appname,
|
|
155
|
+
' package name: ' + config.packagename,
|
|
156
|
+
' organization: ' + config.organization,
|
|
125
157
|
'',
|
|
126
|
-
' in:
|
|
158
|
+
' in: ' + config.projectPath,
|
|
127
159
|
'',
|
|
128
|
-
' from template repo:
|
|
160
|
+
' from template repo: ' + config.templaterepouri
|
|
129
161
|
];
|
|
130
162
|
|
|
131
163
|
if (config.templatepath) {
|
|
132
|
-
details = details.concat([' template path:
|
|
164
|
+
details = details.concat([' template path: ' + config.templatepath]);
|
|
133
165
|
}
|
|
134
166
|
|
|
135
167
|
|
|
136
168
|
if (config.sdkdependencies) {
|
|
137
|
-
details = details.concat([' sdk dependencies:
|
|
169
|
+
details = details.concat([' sdk dependencies: ' + config.sdkdependencies]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// OAuth configuration details
|
|
173
|
+
if (config.consumerkey && config.consumerkey !== '__INSERT_CONSUMER_KEY_HERE__' && config.consumerkey.trim() !== '') {
|
|
174
|
+
details = details.concat([' consumer key: ' + config.consumerkey]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (config.callbackurl && config.callbackurl !== '__INSERT_CALLBACK_URL_HERE__' && config.callbackurl.trim() !== '') {
|
|
178
|
+
details = details.concat([' callback URL: ' + config.callbackurl]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (config.loginserver && config.loginserver.trim() !== '') {
|
|
182
|
+
details = details.concat([' login server: ' + config.loginserver]);
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
// Hybrid extra details
|
|
141
186
|
if (config.apptype.indexOf('hybrid') >= 0) {
|
|
142
187
|
if (config.apptype === 'hybrid_remote') {
|
|
143
|
-
details = details.concat([' start page:
|
|
188
|
+
details = details.concat([' start page: ' + config.startpage]);
|
|
144
189
|
}
|
|
145
190
|
|
|
146
|
-
details = details.concat([' plugin repo:
|
|
191
|
+
details = details.concat([' plugin repo: ' + config.cordovaPluginRepoUri]);
|
|
147
192
|
}
|
|
148
193
|
|
|
149
194
|
utils.logParagraph(details);
|
|
150
195
|
}
|
|
151
196
|
|
|
197
|
+
//
|
|
198
|
+
// Check if valid OAuth configuration is provided
|
|
199
|
+
//
|
|
200
|
+
function hasValidOAuthConfig(config) {
|
|
201
|
+
return config.consumerkey && config.callbackurl &&
|
|
202
|
+
config.consumerkey !== '__INSERT_CONSUMER_KEY_HERE__' &&
|
|
203
|
+
config.callbackurl !== '__INSERT_CALLBACK_URL_HERE__' &&
|
|
204
|
+
config.consumerkey.trim() !== '' &&
|
|
205
|
+
config.callbackurl.trim() !== '' &&
|
|
206
|
+
(!config.loginserver || config.loginserver.trim() !== '');
|
|
207
|
+
}
|
|
208
|
+
|
|
152
209
|
//
|
|
153
210
|
// Print next steps
|
|
154
211
|
//
|
|
155
|
-
function printNextSteps(ide, projectPath, result) {
|
|
212
|
+
function printNextSteps(ide, projectPath, result, hasValidOAuth) {
|
|
156
213
|
var workspacePath = path.join(projectPath, result.workspacePath);
|
|
157
214
|
var bootconfigFile = path.join(projectPath, result.bootconfigFile);
|
|
158
215
|
|
|
216
|
+
var nextSteps = ['Next steps' + (result.platform ? ' for ' + result.platform : '') + ':',
|
|
217
|
+
'',
|
|
218
|
+
'Your application project is ready in ' + projectPath + '.',
|
|
219
|
+
'To use your new application in ' + ide + ', do the following:',
|
|
220
|
+
' - open ' + workspacePath + ' in ' + ide];
|
|
221
|
+
|
|
222
|
+
// Only show OAuth configuration instructions if valid OAuth config was not provided
|
|
223
|
+
if (!hasValidOAuth) {
|
|
224
|
+
nextSteps.push(' - make sure to plug your OAuth Client ID and Callback URI');
|
|
225
|
+
nextSteps.push(' into ' + bootconfigFile);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
nextSteps.push(' - build and run');
|
|
229
|
+
|
|
159
230
|
// Printing out next steps
|
|
160
|
-
utils.logParagraph(
|
|
161
|
-
'',
|
|
162
|
-
'Your application project is ready in ' + projectPath + '.',
|
|
163
|
-
'To use your new application in ' + ide + ', do the following:',
|
|
164
|
-
' - open ' + workspacePath + ' in ' + ide,
|
|
165
|
-
' - build and run',
|
|
166
|
-
'Before you ship, make sure to plug your OAuth Client ID and Callback URI,',
|
|
167
|
-
'and OAuth Scopes into ' + bootconfigFile,
|
|
168
|
-
]);
|
|
231
|
+
utils.logParagraph(nextSteps);
|
|
169
232
|
};
|
|
170
233
|
|
|
171
234
|
//
|
|
172
235
|
// Print next steps for Native Login
|
|
173
236
|
//
|
|
174
|
-
function printNextStepsForNativeLogin(ide, projectPath, result) {
|
|
237
|
+
function printNextStepsForNativeLogin(ide, projectPath, result, hasValidOAuth) {
|
|
175
238
|
var workspacePath = path.join(projectPath, result.workspacePath);
|
|
176
239
|
var bootconfigFile = path.join(projectPath, result.bootconfigFile);
|
|
177
240
|
var entryFile = (ide === 'XCode') ? 'SceneDelegate' : 'MainApplication';
|
|
178
241
|
|
|
242
|
+
var nextSteps = ['Next steps' + (result.platform ? ' for ' + result.platform : '') + ':',
|
|
243
|
+
'',
|
|
244
|
+
'Your application project is ready in ' + projectPath + '.',
|
|
245
|
+
'To use your new application in ' + ide + ', do the following:',
|
|
246
|
+
' - open ' + workspacePath + ' in ' + ide];
|
|
247
|
+
|
|
248
|
+
// Only show OAuth configuration instructions if valid OAuth config was not provided
|
|
249
|
+
if (!hasValidOAuth) {
|
|
250
|
+
nextSteps.push(' - Update the OAuth Client ID, Callback URI, and Community URL in ' + entryFile + ' class.');
|
|
251
|
+
nextSteps.push(' - Make sure to plug your OAuth Client ID and Callback URI into');
|
|
252
|
+
nextSteps.push(' into ' + bootconfigFile);
|
|
253
|
+
nextSteps.push(' since it is still be used for authentication if we fallback on the webview.');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
nextSteps.push(' - build and run');
|
|
257
|
+
|
|
179
258
|
// Printing out next steps
|
|
180
|
-
utils.logParagraph(
|
|
181
|
-
'',
|
|
182
|
-
'Your application project is ready in ' + projectPath + '.',
|
|
183
|
-
'To use your new application in ' + ide + ', do the following:',
|
|
184
|
-
' - open ' + workspacePath + ' in ' + ide,
|
|
185
|
-
' - Update the OAuth Client ID, Callback URI, and Community URL in ' + entryFile + ' class.',
|
|
186
|
-
' - build and run',
|
|
187
|
-
'Before you ship, make sure to plug your OAuth Client ID and Callback URI,',
|
|
188
|
-
'and OAuth Scopes into ' + bootconfigFile + ', since it is still used for',
|
|
189
|
-
'authentication if we fallback on the webview.'
|
|
190
|
-
]);
|
|
259
|
+
utils.logParagraph(nextSteps);
|
|
191
260
|
}
|
|
192
261
|
|
|
193
262
|
//
|
|
@@ -324,13 +393,41 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
324
393
|
config.version = SDK.version;
|
|
325
394
|
|
|
326
395
|
// Figuring out template repo uri and path
|
|
327
|
-
|
|
328
|
-
|
|
396
|
+
let localTemplatesRoot;
|
|
397
|
+
if (config.templatesource) {
|
|
398
|
+
const source = config.templatesource;
|
|
399
|
+
if (fs.existsSync(source)) {
|
|
400
|
+
// Local path to templates suite
|
|
401
|
+
localTemplatesRoot = path.resolve(source);
|
|
402
|
+
if (!config.template) {
|
|
403
|
+
throw new Error('Missing --template when using --templatesource pointing to a local path');
|
|
404
|
+
}
|
|
405
|
+
config.templatepath = config.template;
|
|
406
|
+
// For display purposes
|
|
407
|
+
config.templaterepouri = source;
|
|
408
|
+
} else {
|
|
409
|
+
// Git URL with optional #branch
|
|
410
|
+
const parsed = utils.separateRepoUrlPathBranch(source);
|
|
411
|
+
config.templaterepouri = parsed.repo + '#' + parsed.branch;
|
|
412
|
+
config.templatepath = config.template || parsed.path;
|
|
413
|
+
if (!config.templatepath) {
|
|
414
|
+
throw new Error('Missing template name. Use --template to specify a template within your --templatesource repository.');
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
else if (config.templaterepouri) {
|
|
419
|
+
if (fs.existsSync(config.templaterepouri)) {
|
|
420
|
+
// Local path directly to a specific template directory
|
|
421
|
+
localTemplatesRoot = path.resolve(config.templaterepouri);
|
|
422
|
+
config.templaterepouri = localTemplatesRoot;
|
|
423
|
+
// Use the directory itself as the template root
|
|
424
|
+
config.templatepath = '';
|
|
425
|
+
} else if (!config.templaterepouri.startsWith("https://")) {
|
|
329
426
|
// Given a Mobile SDK template name
|
|
330
427
|
config.templatepath = config.templaterepouri;
|
|
331
428
|
config.templaterepouri = SDK.templatesRepoUri;
|
|
332
429
|
} else {
|
|
333
|
-
// Given a full URI
|
|
430
|
+
// Given a full URI to a specific template path
|
|
334
431
|
var templateUriParsed = utils.separateRepoUrlPathBranch(config.templaterepouri);
|
|
335
432
|
config.templaterepouri = templateUriParsed.repo + '#' + templateUriParsed.branch;
|
|
336
433
|
config.templatepath = templateUriParsed.path;
|
|
@@ -344,10 +441,17 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
344
441
|
// Creating tmp dir for template clone
|
|
345
442
|
var tmpDir = utils.mkTmpDir();
|
|
346
443
|
|
|
347
|
-
//
|
|
348
|
-
var repoDir
|
|
444
|
+
// Resolve template source directory (clone if needed)
|
|
445
|
+
var repoDir;
|
|
446
|
+
if (localTemplatesRoot) {
|
|
447
|
+
repoDir = localTemplatesRoot;
|
|
448
|
+
} else {
|
|
449
|
+
repoDir = utils.cloneRepo(tmpDir, config.templaterepouri);
|
|
450
|
+
}
|
|
349
451
|
config.templateLocalPath = path.join(repoDir, config.templatepath);
|
|
350
452
|
|
|
453
|
+
validateCustomProperties(`${repoDir}/template.json`, config.templateProperties);
|
|
454
|
+
|
|
351
455
|
// Override sdk dependencies in package.json if any were provided
|
|
352
456
|
if (config.sdkdependencies) {
|
|
353
457
|
overrideSdkDependencies(path.join(config.templateLocalPath, 'package.json'), config.sdkdependencies);
|
|
@@ -374,13 +478,14 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
374
478
|
|
|
375
479
|
// Printing next steps
|
|
376
480
|
if (!(results instanceof Array)) { results = [results] };
|
|
481
|
+
var hasValidOAuth = hasValidOAuthConfig(config);
|
|
377
482
|
for (var result of results) {
|
|
378
483
|
var ide = SDK.ides[result.platform || config.platform.split(',')[0]];
|
|
379
484
|
|
|
380
485
|
if (config.templatepath != undefined && config.templatepath.includes('NativeLogin')) {
|
|
381
|
-
printNextStepsForNativeLogin(ide, config.projectPath, result);
|
|
486
|
+
printNextStepsForNativeLogin(ide, config.projectPath, result, hasValidOAuth);
|
|
382
487
|
} else {
|
|
383
|
-
printNextSteps(ide, config.projectPath, result);
|
|
488
|
+
printNextSteps(ide, config.projectPath, result, hasValidOAuth);
|
|
384
489
|
}
|
|
385
490
|
}
|
|
386
491
|
printNextStepsForServerProjectIfNeeded(config.projectPath);
|
|
@@ -392,6 +497,33 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
392
497
|
}
|
|
393
498
|
}
|
|
394
499
|
|
|
500
|
+
function validateCustomProperties(templateJsonPath, customProperties) {
|
|
501
|
+
// skip if template json file does not exist
|
|
502
|
+
if (!fs.existsSync(templateJsonPath)) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
utils.log('Validating custom properties against schema...');
|
|
507
|
+
// Validate data against schema with AJV
|
|
508
|
+
const ajv = new Ajv({allErrors: true});
|
|
509
|
+
const schema = readJsonFile(templateJsonPath);
|
|
510
|
+
const validate = ajv.compile(schema);
|
|
511
|
+
|
|
512
|
+
const jsonToValidate = {
|
|
513
|
+
templatePrerequisites: { templateProperties: customProperties }
|
|
514
|
+
}
|
|
515
|
+
const valid = validate(jsonToValidate);
|
|
516
|
+
|
|
517
|
+
if (!valid) {
|
|
518
|
+
utils.logError('Custom properties validation failed:\n',
|
|
519
|
+
JSON.stringify(validate.errors, null, " "));
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
utils.logInfo('Custom properties are valid\n', COLOR.green);
|
|
524
|
+
}
|
|
525
|
+
|
|
395
526
|
module.exports = {
|
|
396
|
-
createApp
|
|
527
|
+
createApp,
|
|
528
|
+
validateCustomProperties
|
|
397
529
|
};
|
package/shared/jsonChecker.js
CHANGED
package/shared/oclifAdapter.js
CHANGED
|
@@ -30,12 +30,14 @@ const SDK = require('./constants');
|
|
|
30
30
|
const configHelper = require('./configHelper');
|
|
31
31
|
const createHelper = require('./createHelper');
|
|
32
32
|
const templateHelper = require('./templateHelper');
|
|
33
|
+
const { getTemplates, getTemplate, displayTemplateList, displayTemplateDetail } = require('./templateHelper');
|
|
33
34
|
const jsonChecker = require('./jsonChecker');
|
|
34
35
|
const logInfo = require('./utils').logInfo;
|
|
35
36
|
const logError = require('./utils').logError;
|
|
37
|
+
const separateRepoUrlPathBranch = require('./utils').separateRepoUrlPathBranch;
|
|
36
38
|
const os = require('os');
|
|
37
39
|
|
|
38
|
-
const {
|
|
40
|
+
const { SfError } = require('@salesforce/core');
|
|
39
41
|
const { Command, flags } = require('@oclif/command');
|
|
40
42
|
|
|
41
43
|
const namespace = 'mobilesdk';
|
|
@@ -46,21 +48,36 @@ class OclifAdapter extends Command {
|
|
|
46
48
|
return `${description}${os.EOL}${os.EOL}${help}`;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
static listTemplates(cli) {
|
|
50
|
-
const applicableTemplates =
|
|
51
|
+
static listTemplates(cli, templateSourceOrRepoUri, includeDescriptions, outputJson) {
|
|
52
|
+
const applicableTemplates = getTemplates(cli, templateSourceOrRepoUri, includeDescriptions);
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
// Use shared display function
|
|
55
|
+
const commandPrefix = 'sf ' + [namespace, cli.topic, SDK.commands.createwithtemplate.name].join(':');
|
|
56
|
+
const usageExample = '--' + SDK.args.appName.name + '=<YOUR_APP_NAME> --' + SDK.args.packageName.name + '=<YOUR_PACKAGE_NAME> --' + SDK.args.organization.name + '=<YOUR_ORGANIZATION_NAME>';
|
|
57
|
+
displayTemplateList(applicableTemplates, templateSourceOrRepoUri, cli.name, commandPrefix, includeDescriptions, usageExample, outputJson);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static describeTemplate(cli, templateSourceOrRepoUri, templateName, includeDescriptions, outputJson) {
|
|
61
|
+
if (!templateName) {
|
|
62
|
+
logError('Error: Template name is required. Use --template to specify the template name.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const template = getTemplate(templateName, templateSourceOrRepoUri, includeDescriptions);
|
|
67
|
+
|
|
68
|
+
if (!template) {
|
|
69
|
+
logError('Error: Template "' + templateName + '" not found.');
|
|
70
|
+
process.exit(1);
|
|
58
71
|
}
|
|
59
|
-
|
|
72
|
+
|
|
73
|
+
// Use shared display function
|
|
74
|
+
const commandPrefix = 'sf ' + [namespace, cli.topic, SDK.commands.createwithtemplate.name].join(':');
|
|
75
|
+
const usageExample = '--' + SDK.args.appName.name + '=<YOUR_APP_NAME> --' + SDK.args.packageName.name + '=<YOUR_PACKAGE_NAME> --' + SDK.args.organization.name + '=<YOUR_ORGANIZATION_NAME>';
|
|
76
|
+
displayTemplateDetail(template, templateSourceOrRepoUri, cli.name, commandPrefix, includeDescriptions, usageExample, outputJson);
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
static runCommand(cli, commandName, vals) {
|
|
63
|
-
switch(commandName) {
|
|
80
|
+
switch (commandName) {
|
|
64
81
|
case SDK.commands.create.name:
|
|
65
82
|
case SDK.commands.createwithtemplate.name:
|
|
66
83
|
createHelper.createApp(cli, vals);
|
|
@@ -69,7 +86,11 @@ class OclifAdapter extends Command {
|
|
|
69
86
|
configHelper.printVersion(cli);
|
|
70
87
|
break;
|
|
71
88
|
case SDK.commands.listtemplates.name:
|
|
72
|
-
OclifAdapter.listTemplates(cli);
|
|
89
|
+
OclifAdapter.listTemplates(cli, vals.templatesource, vals.doc, vals.json);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
break;
|
|
92
|
+
case SDK.commands.describetemplate.name:
|
|
93
|
+
OclifAdapter.describeTemplate(cli, vals.templatesource, vals.template, vals.doc, vals.json);
|
|
73
94
|
process.exit(0);
|
|
74
95
|
break;
|
|
75
96
|
case SDK.commands.checkconfig.name:
|
|
@@ -139,9 +160,15 @@ class OclifAdapter extends Command {
|
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
execute(cli, klass) {
|
|
142
|
-
const { flags } = this.parse(klass);
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
const { flags, argv : remainingArgs } = this.parse(klass);
|
|
164
|
+
const commandConfig = klass.command;
|
|
165
|
+
if (OclifAdapter.validateCommand(cli, commandConfig.name, flags)) {
|
|
166
|
+
// If the command supports custom flags and there are remaining arguments, parse the custom properties
|
|
167
|
+
if (klass.command.supportCustomFlags === true && remainingArgs.length > 0) {
|
|
168
|
+
const customProperties = OclifAdapter.getCustomProperties(remainingArgs);
|
|
169
|
+
flags.templateProperties = customProperties;
|
|
170
|
+
}
|
|
171
|
+
return OclifAdapter.runCommand(cli, commandConfig.name, flags);
|
|
145
172
|
}
|
|
146
173
|
}
|
|
147
174
|
|
|
@@ -180,16 +207,58 @@ class OclifAdapter extends Command {
|
|
|
180
207
|
this.flags[name] = flag.quantity + '';
|
|
181
208
|
break;
|
|
182
209
|
} else {
|
|
183
|
-
throw new
|
|
210
|
+
throw new SfError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType');
|
|
184
211
|
}
|
|
185
212
|
default:
|
|
186
|
-
throw new
|
|
213
|
+
throw new SfError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType');
|
|
187
214
|
}
|
|
188
215
|
});
|
|
189
216
|
}
|
|
217
|
+
|
|
218
|
+
static getCustomProperties(args) {
|
|
219
|
+
const properties = {};
|
|
220
|
+
const prefix = '--template-property-';
|
|
221
|
+
|
|
222
|
+
for (let i = 0; i < args.length; i++) {
|
|
223
|
+
const arg = args[i];
|
|
224
|
+
|
|
225
|
+
// Check if this argument starts with --template- but not --template-property-
|
|
226
|
+
if (arg.startsWith('--template-') && !arg.startsWith(prefix)) {
|
|
227
|
+
throw new SfError(
|
|
228
|
+
`Invalid template property flag: "${arg}". Template properties must be prefixed with "${prefix}".`,
|
|
229
|
+
'InvalidTemplatePropertyPrefix'
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check if this argument is a template property flag
|
|
234
|
+
if (arg.startsWith(prefix)) {
|
|
235
|
+
// Check if the value is provided with equals sign (--template-property-prop1=val1)
|
|
236
|
+
if (arg.includes('=')) {
|
|
237
|
+
const equalIndex = arg.indexOf('=');
|
|
238
|
+
const propertyName = arg.substring(prefix.length, equalIndex);
|
|
239
|
+
const propertyValue = arg.substring(equalIndex + 1);
|
|
240
|
+
properties[propertyName] = propertyValue;
|
|
241
|
+
} else {
|
|
242
|
+
// Extract the property name (everything after the prefix)
|
|
243
|
+
const propertyName = arg.substring(prefix.length);
|
|
244
|
+
|
|
245
|
+
// Check if there's a next argument and it's not another template property flag
|
|
246
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
247
|
+
// Use the next argument as the value
|
|
248
|
+
properties[propertyName] = args[i + 1];
|
|
249
|
+
i++; // Skip the value in the next iteration
|
|
250
|
+
}
|
|
251
|
+
// If the next argument is another flag or doesn't exist, skip this property
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return properties;
|
|
257
|
+
}
|
|
258
|
+
|
|
190
259
|
}
|
|
191
260
|
|
|
192
|
-
OclifAdapter.getCommand = function(cli, commandName) {
|
|
261
|
+
OclifAdapter.getCommand = function (cli, commandName) {
|
|
193
262
|
if (!this._command) {
|
|
194
263
|
this._command = configHelper.getCommandExpanded(cli, commandName);
|
|
195
264
|
}
|
package/shared/templateHelper.js
CHANGED
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
// Dependencies
|
|
29
29
|
var path = require('path'),
|
|
30
30
|
SDK = require('./constants'),
|
|
31
|
-
utils = require('./utils')
|
|
31
|
+
utils = require('./utils'),
|
|
32
|
+
fs = require('fs'),
|
|
33
|
+
{ constantCase } = require('change-case');
|
|
32
34
|
|
|
33
35
|
//
|
|
34
36
|
// Helper to prepare template
|
|
@@ -36,7 +38,7 @@ var path = require('path'),
|
|
|
36
38
|
function prepareTemplate(config, templateDir) {
|
|
37
39
|
var template = require(path.join(templateDir, 'template.js'));
|
|
38
40
|
return utils.runFunctionThrowError(
|
|
39
|
-
function() {
|
|
41
|
+
function () {
|
|
40
42
|
return template.prepare(config, utils.replaceInFiles, utils.moveFile, utils.removeFile);
|
|
41
43
|
},
|
|
42
44
|
templateDir);
|
|
@@ -45,14 +47,22 @@ function prepareTemplate(config, templateDir) {
|
|
|
45
47
|
//
|
|
46
48
|
// Get templates for the given cli
|
|
47
49
|
//
|
|
48
|
-
function getTemplates(cli) {
|
|
50
|
+
function getTemplates(cli, templateSourceOrRepoUri, includeDescriptions) {
|
|
49
51
|
try {
|
|
50
52
|
|
|
51
53
|
// Creating tmp dir for template clone
|
|
52
54
|
var tmpDir = utils.mkTmpDir();
|
|
53
55
|
|
|
54
|
-
//
|
|
55
|
-
var
|
|
56
|
+
// Use provided source (git URL or local path) or fall back to default
|
|
57
|
+
var source = templateSourceOrRepoUri || SDK.templatesRepoUri;
|
|
58
|
+
var repoDir;
|
|
59
|
+
if (fs.existsSync(source)) {
|
|
60
|
+
// Local path
|
|
61
|
+
repoDir = path.resolve(source);
|
|
62
|
+
} else {
|
|
63
|
+
// Git URL
|
|
64
|
+
repoDir = utils.cloneRepo(tmpDir, source);
|
|
65
|
+
}
|
|
56
66
|
|
|
57
67
|
// Getting list of templates
|
|
58
68
|
var templates = require(path.join(repoDir, 'templates.json'));
|
|
@@ -61,6 +71,16 @@ function getTemplates(cli) {
|
|
|
61
71
|
var applicableTemplates = templates
|
|
62
72
|
.filter(template => cli.appTypes.includes(template.appType) && cli.platforms.filter(platform => template.platforms.includes(platform)).length > 0);
|
|
63
73
|
|
|
74
|
+
// Add metadata and custom properties to each template
|
|
75
|
+
applicableTemplates.forEach(function (template) {
|
|
76
|
+
const metadata = getTemplateMetadata(template.path, repoDir);
|
|
77
|
+
template.customProperties = getCustomProperties(metadata);
|
|
78
|
+
// If descriptions are requested, add metadata
|
|
79
|
+
if (includeDescriptions) {
|
|
80
|
+
template.metadata = metadata;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
64
84
|
// Cleanup
|
|
65
85
|
utils.removeFile(tmpDir);
|
|
66
86
|
|
|
@@ -96,9 +116,255 @@ function getAppTypeFromTemplate(templateRepoUriWithPossiblePath) {
|
|
|
96
116
|
return appType;
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
//
|
|
120
|
+
// Extract template metadata from template.json file
|
|
121
|
+
//
|
|
122
|
+
function getTemplateMetadata(templatePath, repoDir) {
|
|
123
|
+
try {
|
|
124
|
+
var templateJsonPath = path.join(repoDir, templatePath, 'template.json');
|
|
125
|
+
if (fs.existsSync(templateJsonPath)) {
|
|
126
|
+
var templateJsonContent = fs.readFileSync(templateJsonPath, 'utf8');
|
|
127
|
+
var templateData = JSON.parse(templateJsonContent);
|
|
128
|
+
|
|
129
|
+
// Return all metadata properties from the template.json file
|
|
130
|
+
// This makes the function more flexible for future use cases
|
|
131
|
+
return templateData;
|
|
132
|
+
}
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// If there's any error reading or parsing the template.json, just return null
|
|
135
|
+
// This ensures the command continues to work even if template.json parsing fails
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
//
|
|
141
|
+
// Get a single template by name
|
|
142
|
+
//
|
|
143
|
+
function getTemplate(templateName, templateSourceOrRepoUri, includeDescriptions) {
|
|
144
|
+
try {
|
|
145
|
+
// Creating tmp dir for template clone
|
|
146
|
+
var tmpDir = utils.mkTmpDir();
|
|
147
|
+
|
|
148
|
+
// Use provided source (git URL or local path) or fall back to default
|
|
149
|
+
var source = templateSourceOrRepoUri || SDK.templatesRepoUri;
|
|
150
|
+
var repoDir;
|
|
151
|
+
if (fs.existsSync(source)) {
|
|
152
|
+
// Local path
|
|
153
|
+
repoDir = path.resolve(source);
|
|
154
|
+
} else {
|
|
155
|
+
// Git URL
|
|
156
|
+
repoDir = utils.cloneRepo(tmpDir, source);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Getting list of templates
|
|
160
|
+
var templates = require(path.join(repoDir, 'templates.json'));
|
|
161
|
+
|
|
162
|
+
// Finding the specific template
|
|
163
|
+
var template = templates.find(t => t.path === templateName);
|
|
164
|
+
|
|
165
|
+
if (!template) {
|
|
166
|
+
utils.removeFile(tmpDir);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const metadata = getTemplateMetadata(template.path, repoDir);
|
|
171
|
+
template.customProperties = getCustomProperties(metadata);
|
|
172
|
+
|
|
173
|
+
// If descriptions are requested, add metadata
|
|
174
|
+
if (includeDescriptions) {
|
|
175
|
+
template.metadata = metadata;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Cleanup
|
|
179
|
+
utils.removeFile(tmpDir);
|
|
180
|
+
|
|
181
|
+
return template;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
//
|
|
188
|
+
// Build template command string
|
|
189
|
+
//
|
|
190
|
+
function buildTemplateCommand(source, commandPrefix, templatePath, extraRequiredArgs, customProperties) {
|
|
191
|
+
var sourceForCommand = source || SDK.templatesRepoUri;
|
|
192
|
+
var command = commandPrefix + ' --' + SDK.args.templateSource.name + '=' + sourceForCommand
|
|
193
|
+
+ ' --' + SDK.args.template.name + '=' + templatePath;
|
|
194
|
+
|
|
195
|
+
if (extraRequiredArgs) {
|
|
196
|
+
command += ` ${extraRequiredArgs}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Add custom command args if provided
|
|
200
|
+
if (customProperties) {
|
|
201
|
+
const customCommandArgs = getCustomCommandArgs(customProperties);
|
|
202
|
+
if (customCommandArgs.length > 0) {
|
|
203
|
+
command += ` ${customCommandArgs.join(' ')}`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return command;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//
|
|
211
|
+
// Display template list with optional metadata
|
|
212
|
+
//
|
|
213
|
+
function displayTemplateList(templates, source, cliName, commandPrefix, includeDescriptions, extraRequiredArgs, outputJson) {
|
|
214
|
+
var COLOR = require('./outputColors');
|
|
215
|
+
var logInfo = utils.logInfo;
|
|
216
|
+
|
|
217
|
+
if (outputJson) {
|
|
218
|
+
// Output in JSON format
|
|
219
|
+
var jsonOutput = {
|
|
220
|
+
repository: source || 'default',
|
|
221
|
+
templates: templates.map(function (template, index) {
|
|
222
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
223
|
+
|
|
224
|
+
var jsonTemplate = {
|
|
225
|
+
index: index + 1,
|
|
226
|
+
path: template.path,
|
|
227
|
+
description: template.description,
|
|
228
|
+
appType: template.appType,
|
|
229
|
+
platforms: template.platforms,
|
|
230
|
+
command: command
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (includeDescriptions && template.metadata) {
|
|
234
|
+
jsonTemplate.metadata = template.metadata;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return jsonTemplate;
|
|
238
|
+
})
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
logInfo(JSON.stringify(jsonOutput, null, 2), COLOR.white);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Show which template repository is being used
|
|
246
|
+
if (source) {
|
|
247
|
+
logInfo('\nAvailable templates from custom repository:\n', COLOR.cyan);
|
|
248
|
+
logInfo('Repository: ' + source, COLOR.cyan);
|
|
249
|
+
} else {
|
|
250
|
+
logInfo('\nAvailable templates:\n', COLOR.cyan);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (var i = 0; i < templates.length; i++) {
|
|
254
|
+
var template = templates[i];
|
|
255
|
+
logInfo((i + 1) + ') ' + template.description, COLOR.cyan);
|
|
256
|
+
|
|
257
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
258
|
+
|
|
259
|
+
logInfo(command, COLOR.magenta);
|
|
260
|
+
|
|
261
|
+
// If descriptions are requested and available, show them
|
|
262
|
+
if (includeDescriptions && template.metadata) {
|
|
263
|
+
if (template.metadata.description) {
|
|
264
|
+
logInfo(' Description: ' + template.metadata.description, COLOR.white);
|
|
265
|
+
}
|
|
266
|
+
if (template.metadata.useCase) {
|
|
267
|
+
logInfo(' Use Case: ' + template.metadata.useCase, COLOR.white);
|
|
268
|
+
}
|
|
269
|
+
if (template.metadata.features && Array.isArray(template.metadata.features)) {
|
|
270
|
+
logInfo(' Features: ' + template.metadata.features.join(', '), COLOR.white);
|
|
271
|
+
}
|
|
272
|
+
if (template.metadata.complexity) {
|
|
273
|
+
logInfo(' Complexity: ' + template.metadata.complexity, COLOR.white);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
logInfo('');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
//
|
|
281
|
+
// Display detailed information about a single template
|
|
282
|
+
//
|
|
283
|
+
function displayTemplateDetail(template, source, cliName, commandPrefix, includeDescriptions, extraRequiredArgs, outputJson) {
|
|
284
|
+
var COLOR = require('./outputColors');
|
|
285
|
+
var logInfo = utils.logInfo;
|
|
286
|
+
|
|
287
|
+
// create command usage
|
|
288
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
289
|
+
|
|
290
|
+
if (outputJson) {
|
|
291
|
+
// Output in JSON format
|
|
292
|
+
var jsonOutput = {
|
|
293
|
+
repository: source || 'default',
|
|
294
|
+
template: {
|
|
295
|
+
path: template.path,
|
|
296
|
+
description: template.description,
|
|
297
|
+
appType: template.appType,
|
|
298
|
+
platforms: template.platforms,
|
|
299
|
+
command: command
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (includeDescriptions && template.metadata) {
|
|
304
|
+
jsonOutput.template.metadata = template.metadata;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
logInfo(JSON.stringify(jsonOutput, null, 2), COLOR.white);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Show which template repository is being used
|
|
312
|
+
if (source) {
|
|
313
|
+
logInfo('\nTemplate from custom repository:\n', COLOR.cyan);
|
|
314
|
+
logInfo('Repository: ' + source, COLOR.cyan);
|
|
315
|
+
} else {
|
|
316
|
+
logInfo('\nTemplate from default repository:\n', COLOR.cyan);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Display template basic info
|
|
320
|
+
logInfo('Template: ' + template.path, COLOR.cyan);
|
|
321
|
+
logInfo('Description: ' + template.description, COLOR.cyan);
|
|
322
|
+
logInfo('App Type: ' + template.appType, COLOR.cyan);
|
|
323
|
+
logInfo('Platforms: ' + template.platforms.join(', '), COLOR.cyan);
|
|
324
|
+
logInfo('\nUsage:', COLOR.magenta);
|
|
325
|
+
|
|
326
|
+
// Display command usage
|
|
327
|
+
logInfo(command, COLOR.magenta);
|
|
328
|
+
|
|
329
|
+
// If descriptions are requested and available, show raw JSON metadata
|
|
330
|
+
if (includeDescriptions && template.metadata) {
|
|
331
|
+
logInfo('\nTemplate Metadata (template.json):', COLOR.cyan);
|
|
332
|
+
logInfo(JSON.stringify(template.metadata, null, 2), COLOR.white);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
logInfo('');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get custom properties from metadata
|
|
340
|
+
* @param {Object} metadata - Template schema json object
|
|
341
|
+
* @returns {Object} Custom properties object
|
|
342
|
+
*/
|
|
343
|
+
function getCustomProperties(metadata) {
|
|
344
|
+
return metadata?.properties?.templatePrerequisites?.properties?.templateProperties || null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get custom command args from custom properties metadata
|
|
349
|
+
* @param {JSON} customPropertiesMetadata - Custom properties metadata
|
|
350
|
+
* @returns {Array} Custom command args
|
|
351
|
+
*/
|
|
352
|
+
function getCustomCommandArgs(customPropertiesMetadata) {
|
|
353
|
+
const properties = customPropertiesMetadata?.properties;
|
|
354
|
+
if (!properties) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
return Object.keys(properties).map(key => `--template-property-${key}=<${constantCase(key)}>`);
|
|
358
|
+
}
|
|
99
359
|
|
|
100
360
|
module.exports = {
|
|
101
361
|
prepareTemplate,
|
|
102
362
|
getTemplates,
|
|
103
|
-
|
|
363
|
+
getTemplate,
|
|
364
|
+
getAppTypeFromTemplate,
|
|
365
|
+
getTemplateMetadata,
|
|
366
|
+
displayTemplateList,
|
|
367
|
+
displayTemplateDetail,
|
|
368
|
+
getCustomProperties,
|
|
369
|
+
getCustomCommandArgs
|
|
104
370
|
};
|