forceios 13.1.0 → 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 +2 -1
- package/shared/constants.js +48 -8
- package/shared/createHelper.js +106 -35
- package/shared/jsonChecker.js +2 -1
- package/shared/oclifAdapter.js +54 -6
- package/shared/templateHelper.js +81 -58
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.
|
|
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
|
@@ -73,7 +73,8 @@ function getCommandExpanded(cli, commandName) {
|
|
|
73
73
|
args: getArgsExpanded(cli, commandName),
|
|
74
74
|
description: applyCli(command.description, cli),
|
|
75
75
|
longDescription: applyCli(command.longDescription, cli),
|
|
76
|
-
help: applyCli(command.help, cli)
|
|
76
|
+
help: applyCli(command.help, cli),
|
|
77
|
+
supportCustomFlags: command.supportCustomFlags
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
|
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.
|
|
31
|
+
var VERSION= '13.2.0-alpha.0';
|
|
32
32
|
|
|
33
33
|
module.exports = {
|
|
34
34
|
version: VERSION,
|
|
@@ -60,9 +60,9 @@ 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
68
|
android: '14.0.1'
|
|
@@ -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: {
|
|
@@ -140,7 +140,7 @@ module.exports = {
|
|
|
140
140
|
commands: ['create', 'createwithtemplate', 'version', 'listtemplates', 'describetemplate', 'checkconfig']
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
args: {
|
|
145
145
|
platform: {
|
|
146
146
|
name: 'platform',
|
|
@@ -296,7 +296,7 @@ module.exports = {
|
|
|
296
296
|
type: 'string',
|
|
297
297
|
hidden: true
|
|
298
298
|
},
|
|
299
|
-
|
|
299
|
+
sdkDependencies: {
|
|
300
300
|
name: 'sdkdependencies',
|
|
301
301
|
description: 'override sdk dependencies',
|
|
302
302
|
'char': 'd',
|
|
@@ -329,6 +329,39 @@ module.exports = {
|
|
|
329
329
|
required: false,
|
|
330
330
|
type: 'boolean',
|
|
331
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'
|
|
332
365
|
}
|
|
333
366
|
},
|
|
334
367
|
|
|
@@ -342,6 +375,9 @@ module.exports = {
|
|
|
342
375
|
'organization',
|
|
343
376
|
cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null,
|
|
344
377
|
'outputDir',
|
|
378
|
+
'consumerKey',
|
|
379
|
+
'callbackURL',
|
|
380
|
+
'loginServer',
|
|
345
381
|
'verbose',
|
|
346
382
|
cli.name === 'forcehybrid' ? 'pluginRepoUri' : null,
|
|
347
383
|
'sdkDependencies'
|
|
@@ -361,13 +397,17 @@ module.exports = {
|
|
|
361
397
|
'organization',
|
|
362
398
|
cli.appTypes.indexOf('hybrid_remote') >=0 ? 'startPage' : null,
|
|
363
399
|
'outputDir',
|
|
400
|
+
'consumerKey',
|
|
401
|
+
'callbackURL',
|
|
402
|
+
'loginServer',
|
|
364
403
|
'verbose',
|
|
365
404
|
cli.name === 'forcehybrid' ? 'pluginRepoUri' : null,
|
|
366
405
|
'sdkDependencies'
|
|
367
406
|
].filter(x=>x!=null),
|
|
368
407
|
description: cli => 'create ' + cli.purpose + ' from a template',
|
|
369
408
|
longDescription: cli => 'Create ' + cli.purpose + ' from a template.',
|
|
370
|
-
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
|
|
371
411
|
},
|
|
372
412
|
version: {
|
|
373
413
|
name: 'version',
|
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';
|
|
@@ -147,75 +151,112 @@ function createAndroidAPI35Theme(projectDir) {
|
|
|
147
151
|
function printDetails(config) {
|
|
148
152
|
// Printing out details
|
|
149
153
|
var details = ['Creating ' + config.platform.replace(',', ' and ') + ' ' + config.apptype + ' application using Salesforce Mobile SDK',
|
|
150
|
-
' with app name:
|
|
151
|
-
' package name:
|
|
152
|
-
' organization:
|
|
154
|
+
' with app name: ' + config.appname,
|
|
155
|
+
' package name: ' + config.packagename,
|
|
156
|
+
' organization: ' + config.organization,
|
|
153
157
|
'',
|
|
154
|
-
' in:
|
|
158
|
+
' in: ' + config.projectPath,
|
|
155
159
|
'',
|
|
156
|
-
' from template repo:
|
|
160
|
+
' from template repo: ' + config.templaterepouri
|
|
157
161
|
];
|
|
158
162
|
|
|
159
163
|
if (config.templatepath) {
|
|
160
|
-
details = details.concat([' template path:
|
|
164
|
+
details = details.concat([' template path: ' + config.templatepath]);
|
|
161
165
|
}
|
|
162
166
|
|
|
163
167
|
|
|
164
168
|
if (config.sdkdependencies) {
|
|
165
|
-
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]);
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
// Hybrid extra details
|
|
169
186
|
if (config.apptype.indexOf('hybrid') >= 0) {
|
|
170
187
|
if (config.apptype === 'hybrid_remote') {
|
|
171
|
-
details = details.concat([' start page:
|
|
188
|
+
details = details.concat([' start page: ' + config.startpage]);
|
|
172
189
|
}
|
|
173
190
|
|
|
174
|
-
details = details.concat([' plugin repo:
|
|
191
|
+
details = details.concat([' plugin repo: ' + config.cordovaPluginRepoUri]);
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
utils.logParagraph(details);
|
|
178
195
|
}
|
|
179
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
|
+
|
|
180
209
|
//
|
|
181
210
|
// Print next steps
|
|
182
211
|
//
|
|
183
|
-
function printNextSteps(ide, projectPath, result) {
|
|
212
|
+
function printNextSteps(ide, projectPath, result, hasValidOAuth) {
|
|
184
213
|
var workspacePath = path.join(projectPath, result.workspacePath);
|
|
185
214
|
var bootconfigFile = path.join(projectPath, result.bootconfigFile);
|
|
186
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
|
+
|
|
187
230
|
// Printing out next steps
|
|
188
|
-
utils.logParagraph(
|
|
189
|
-
'',
|
|
190
|
-
'Your application project is ready in ' + projectPath + '.',
|
|
191
|
-
'To use your new application in ' + ide + ', do the following:',
|
|
192
|
-
' - open ' + workspacePath + ' in ' + ide,
|
|
193
|
-
' - build and run',
|
|
194
|
-
'Before you ship, make sure to plug your OAuth Client ID and Callback URI,',
|
|
195
|
-
'and OAuth Scopes into ' + bootconfigFile,
|
|
196
|
-
]);
|
|
231
|
+
utils.logParagraph(nextSteps);
|
|
197
232
|
};
|
|
198
233
|
|
|
199
234
|
//
|
|
200
235
|
// Print next steps for Native Login
|
|
201
236
|
//
|
|
202
|
-
function printNextStepsForNativeLogin(ide, projectPath, result) {
|
|
237
|
+
function printNextStepsForNativeLogin(ide, projectPath, result, hasValidOAuth) {
|
|
203
238
|
var workspacePath = path.join(projectPath, result.workspacePath);
|
|
204
239
|
var bootconfigFile = path.join(projectPath, result.bootconfigFile);
|
|
205
240
|
var entryFile = (ide === 'XCode') ? 'SceneDelegate' : 'MainApplication';
|
|
206
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
|
+
|
|
207
258
|
// Printing out next steps
|
|
208
|
-
utils.logParagraph(
|
|
209
|
-
'',
|
|
210
|
-
'Your application project is ready in ' + projectPath + '.',
|
|
211
|
-
'To use your new application in ' + ide + ', do the following:',
|
|
212
|
-
' - open ' + workspacePath + ' in ' + ide,
|
|
213
|
-
' - Update the OAuth Client ID, Callback URI, and Community URL in ' + entryFile + ' class.',
|
|
214
|
-
' - build and run',
|
|
215
|
-
'Before you ship, make sure to plug your OAuth Client ID and Callback URI,',
|
|
216
|
-
'and OAuth Scopes into ' + bootconfigFile + ', since it is still used for',
|
|
217
|
-
'authentication if we fallback on the webview.'
|
|
218
|
-
]);
|
|
259
|
+
utils.logParagraph(nextSteps);
|
|
219
260
|
}
|
|
220
261
|
|
|
221
262
|
//
|
|
@@ -409,6 +450,8 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
409
450
|
}
|
|
410
451
|
config.templateLocalPath = path.join(repoDir, config.templatepath);
|
|
411
452
|
|
|
453
|
+
validateCustomProperties(`${repoDir}/template.json`, config.templateProperties);
|
|
454
|
+
|
|
412
455
|
// Override sdk dependencies in package.json if any were provided
|
|
413
456
|
if (config.sdkdependencies) {
|
|
414
457
|
overrideSdkDependencies(path.join(config.templateLocalPath, 'package.json'), config.sdkdependencies);
|
|
@@ -435,13 +478,14 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
435
478
|
|
|
436
479
|
// Printing next steps
|
|
437
480
|
if (!(results instanceof Array)) { results = [results] };
|
|
481
|
+
var hasValidOAuth = hasValidOAuthConfig(config);
|
|
438
482
|
for (var result of results) {
|
|
439
483
|
var ide = SDK.ides[result.platform || config.platform.split(',')[0]];
|
|
440
484
|
|
|
441
485
|
if (config.templatepath != undefined && config.templatepath.includes('NativeLogin')) {
|
|
442
|
-
printNextStepsForNativeLogin(ide, config.projectPath, result);
|
|
486
|
+
printNextStepsForNativeLogin(ide, config.projectPath, result, hasValidOAuth);
|
|
443
487
|
} else {
|
|
444
|
-
printNextSteps(ide, config.projectPath, result);
|
|
488
|
+
printNextSteps(ide, config.projectPath, result, hasValidOAuth);
|
|
445
489
|
}
|
|
446
490
|
}
|
|
447
491
|
printNextStepsForServerProjectIfNeeded(config.projectPath);
|
|
@@ -453,6 +497,33 @@ function actuallyCreateApp(forcecli, config) {
|
|
|
453
497
|
}
|
|
454
498
|
}
|
|
455
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
|
+
|
|
456
526
|
module.exports = {
|
|
457
|
-
createApp
|
|
527
|
+
createApp,
|
|
528
|
+
validateCustomProperties
|
|
458
529
|
};
|
package/shared/jsonChecker.js
CHANGED
package/shared/oclifAdapter.js
CHANGED
|
@@ -37,7 +37,7 @@ const logError = require('./utils').logError;
|
|
|
37
37
|
const separateRepoUrlPathBranch = require('./utils').separateRepoUrlPathBranch;
|
|
38
38
|
const os = require('os');
|
|
39
39
|
|
|
40
|
-
const {
|
|
40
|
+
const { SfError } = require('@salesforce/core');
|
|
41
41
|
const { Command, flags } = require('@oclif/command');
|
|
42
42
|
|
|
43
43
|
const namespace = 'mobilesdk';
|
|
@@ -160,9 +160,15 @@ class OclifAdapter extends Command {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
execute(cli, klass) {
|
|
163
|
-
const { flags } = this.parse(klass);
|
|
164
|
-
|
|
165
|
-
|
|
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);
|
|
166
172
|
}
|
|
167
173
|
}
|
|
168
174
|
|
|
@@ -201,13 +207,55 @@ class OclifAdapter extends Command {
|
|
|
201
207
|
this.flags[name] = flag.quantity + '';
|
|
202
208
|
break;
|
|
203
209
|
} else {
|
|
204
|
-
throw new
|
|
210
|
+
throw new SfError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType');
|
|
205
211
|
}
|
|
206
212
|
default:
|
|
207
|
-
throw new
|
|
213
|
+
throw new SfError(`Unexpected value type for flag ${name}`, 'UnexpectedFlagValueType');
|
|
208
214
|
}
|
|
209
215
|
});
|
|
210
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
|
+
|
|
211
259
|
}
|
|
212
260
|
|
|
213
261
|
OclifAdapter.getCommand = function (cli, commandName) {
|
package/shared/templateHelper.js
CHANGED
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
var path = require('path'),
|
|
30
30
|
SDK = require('./constants'),
|
|
31
31
|
utils = require('./utils'),
|
|
32
|
-
fs = require('fs')
|
|
32
|
+
fs = require('fs'),
|
|
33
|
+
{ constantCase } = require('change-case');
|
|
33
34
|
|
|
34
35
|
//
|
|
35
36
|
// Helper to prepare template
|
|
@@ -70,12 +71,15 @@ function getTemplates(cli, templateSourceOrRepoUri, includeDescriptions) {
|
|
|
70
71
|
var applicableTemplates = templates
|
|
71
72
|
.filter(template => cli.appTypes.includes(template.appType) && cli.platforms.filter(platform => template.platforms.includes(platform)).length > 0);
|
|
72
73
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
});
|
|
79
83
|
|
|
80
84
|
// Cleanup
|
|
81
85
|
utils.removeFile(tmpDir);
|
|
@@ -121,7 +125,7 @@ function getTemplateMetadata(templatePath, repoDir) {
|
|
|
121
125
|
if (fs.existsSync(templateJsonPath)) {
|
|
122
126
|
var templateJsonContent = fs.readFileSync(templateJsonPath, 'utf8');
|
|
123
127
|
var templateData = JSON.parse(templateJsonContent);
|
|
124
|
-
|
|
128
|
+
|
|
125
129
|
// Return all metadata properties from the template.json file
|
|
126
130
|
// This makes the function more flexible for future use cases
|
|
127
131
|
return templateData;
|
|
@@ -157,47 +161,65 @@ function getTemplate(templateName, templateSourceOrRepoUri, includeDescriptions)
|
|
|
157
161
|
|
|
158
162
|
// Finding the specific template
|
|
159
163
|
var template = templates.find(t => t.path === templateName);
|
|
160
|
-
|
|
164
|
+
|
|
161
165
|
if (!template) {
|
|
162
166
|
utils.removeFile(tmpDir);
|
|
163
167
|
return null;
|
|
164
168
|
}
|
|
165
|
-
|
|
169
|
+
|
|
170
|
+
const metadata = getTemplateMetadata(template.path, repoDir);
|
|
171
|
+
template.customProperties = getCustomProperties(metadata);
|
|
172
|
+
|
|
166
173
|
// If descriptions are requested, add metadata
|
|
167
174
|
if (includeDescriptions) {
|
|
168
|
-
template.metadata =
|
|
175
|
+
template.metadata = metadata;
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
|
|
171
178
|
// Cleanup
|
|
172
179
|
utils.removeFile(tmpDir);
|
|
173
|
-
|
|
180
|
+
|
|
174
181
|
return template;
|
|
175
182
|
} catch (error) {
|
|
176
183
|
return null;
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
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
|
+
|
|
180
210
|
//
|
|
181
211
|
// Display template list with optional metadata
|
|
182
212
|
//
|
|
183
213
|
function displayTemplateList(templates, source, cliName, commandPrefix, includeDescriptions, extraRequiredArgs, outputJson) {
|
|
184
|
-
var utils = require('./utils');
|
|
185
214
|
var COLOR = require('./outputColors');
|
|
186
215
|
var logInfo = utils.logInfo;
|
|
187
|
-
var SDK = require('./constants');
|
|
188
216
|
|
|
189
217
|
if (outputJson) {
|
|
190
218
|
// Output in JSON format
|
|
191
219
|
var jsonOutput = {
|
|
192
220
|
repository: source || 'default',
|
|
193
|
-
templates: templates.map(function(template, index) {
|
|
194
|
-
var
|
|
195
|
-
var command = commandPrefix + ' --' + SDK.args.templateSource.name + '=' + sourceForCommand
|
|
196
|
-
+ ' --' + SDK.args.template.name + '=' + template.path;
|
|
197
|
-
|
|
198
|
-
if (extraRequiredArgs) {
|
|
199
|
-
command += ` ${extraRequiredArgs}`;
|
|
200
|
-
}
|
|
221
|
+
templates: templates.map(function (template, index) {
|
|
222
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
201
223
|
|
|
202
224
|
var jsonTemplate = {
|
|
203
225
|
index: index + 1,
|
|
@@ -215,7 +237,7 @@ function displayTemplateList(templates, source, cliName, commandPrefix, includeD
|
|
|
215
237
|
return jsonTemplate;
|
|
216
238
|
})
|
|
217
239
|
};
|
|
218
|
-
|
|
240
|
+
|
|
219
241
|
logInfo(JSON.stringify(jsonOutput, null, 2), COLOR.white);
|
|
220
242
|
return;
|
|
221
243
|
}
|
|
@@ -232,17 +254,10 @@ function displayTemplateList(templates, source, cliName, commandPrefix, includeD
|
|
|
232
254
|
var template = templates[i];
|
|
233
255
|
logInfo((i + 1) + ') ' + template.description, COLOR.cyan);
|
|
234
256
|
|
|
235
|
-
var
|
|
236
|
-
|
|
237
|
-
+ ' --' + SDK.args.template.name + '=' + template.path;
|
|
238
|
-
|
|
239
|
-
// Add additional required args if provided
|
|
240
|
-
if (extraRequiredArgs) {
|
|
241
|
-
command += ` ${extraRequiredArgs}`;
|
|
242
|
-
}
|
|
243
|
-
|
|
257
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
258
|
+
|
|
244
259
|
logInfo(command, COLOR.magenta);
|
|
245
|
-
|
|
260
|
+
|
|
246
261
|
// If descriptions are requested and available, show them
|
|
247
262
|
if (includeDescriptions && template.metadata) {
|
|
248
263
|
if (template.metadata.description) {
|
|
@@ -266,21 +281,14 @@ function displayTemplateList(templates, source, cliName, commandPrefix, includeD
|
|
|
266
281
|
// Display detailed information about a single template
|
|
267
282
|
//
|
|
268
283
|
function displayTemplateDetail(template, source, cliName, commandPrefix, includeDescriptions, extraRequiredArgs, outputJson) {
|
|
269
|
-
var utils = require('./utils');
|
|
270
284
|
var COLOR = require('./outputColors');
|
|
271
285
|
var logInfo = utils.logInfo;
|
|
272
|
-
|
|
286
|
+
|
|
287
|
+
// create command usage
|
|
288
|
+
var command = buildTemplateCommand(source, commandPrefix, template.path, extraRequiredArgs, template.customProperties);
|
|
273
289
|
|
|
274
290
|
if (outputJson) {
|
|
275
291
|
// Output in JSON format
|
|
276
|
-
var sourceForCommand = source || SDK.templatesRepoUri;
|
|
277
|
-
var command = commandPrefix + ' --' + SDK.args.templateSource.name + '=' + sourceForCommand
|
|
278
|
-
+ ' --' + SDK.args.template.name + '=' + template.path;
|
|
279
|
-
|
|
280
|
-
if (extraRequiredArgs) {
|
|
281
|
-
command += ` ${extraRequiredArgs}`;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
292
|
var jsonOutput = {
|
|
285
293
|
repository: source || 'default',
|
|
286
294
|
template: {
|
|
@@ -295,7 +303,7 @@ function displayTemplateDetail(template, source, cliName, commandPrefix, include
|
|
|
295
303
|
if (includeDescriptions && template.metadata) {
|
|
296
304
|
jsonOutput.template.metadata = template.metadata;
|
|
297
305
|
}
|
|
298
|
-
|
|
306
|
+
|
|
299
307
|
logInfo(JSON.stringify(jsonOutput, null, 2), COLOR.white);
|
|
300
308
|
return;
|
|
301
309
|
}
|
|
@@ -313,29 +321,42 @@ function displayTemplateDetail(template, source, cliName, commandPrefix, include
|
|
|
313
321
|
logInfo('Description: ' + template.description, COLOR.cyan);
|
|
314
322
|
logInfo('App Type: ' + template.appType, COLOR.cyan);
|
|
315
323
|
logInfo('Platforms: ' + template.platforms.join(', '), COLOR.cyan);
|
|
324
|
+
logInfo('\nUsage:', COLOR.magenta);
|
|
316
325
|
|
|
317
326
|
// Display command usage
|
|
318
|
-
var sourceForCommand = source || SDK.templatesRepoUri;
|
|
319
|
-
var command = commandPrefix + ' --' + SDK.args.templateSource.name + '=' + sourceForCommand
|
|
320
|
-
+ ' --' + SDK.args.template.name + '=' + template.path;
|
|
321
|
-
|
|
322
|
-
// Add additional required args if provided
|
|
323
|
-
if (extraRequiredArgs) {
|
|
324
|
-
command += ` ${extraRequiredArgs}`;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
logInfo('\nUsage:', COLOR.magenta);
|
|
328
327
|
logInfo(command, COLOR.magenta);
|
|
329
|
-
|
|
328
|
+
|
|
330
329
|
// If descriptions are requested and available, show raw JSON metadata
|
|
331
330
|
if (includeDescriptions && template.metadata) {
|
|
332
331
|
logInfo('\nTemplate Metadata (template.json):', COLOR.cyan);
|
|
333
332
|
logInfo(JSON.stringify(template.metadata, null, 2), COLOR.white);
|
|
334
333
|
}
|
|
335
|
-
|
|
334
|
+
|
|
336
335
|
logInfo('');
|
|
337
336
|
}
|
|
338
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
|
+
}
|
|
359
|
+
|
|
339
360
|
module.exports = {
|
|
340
361
|
prepareTemplate,
|
|
341
362
|
getTemplates,
|
|
@@ -343,5 +364,7 @@ module.exports = {
|
|
|
343
364
|
getAppTypeFromTemplate,
|
|
344
365
|
getTemplateMetadata,
|
|
345
366
|
displayTemplateList,
|
|
346
|
-
displayTemplateDetail
|
|
367
|
+
displayTemplateDetail,
|
|
368
|
+
getCustomProperties,
|
|
369
|
+
getCustomCommandArgs
|
|
347
370
|
};
|