@iexec/iapp-maker 0.0.1-alpha-nightly-c808af829ce4b10cbbecdbc64ec7fa9b8cf3558d → 0.0.1-alpha-nightly-6be476717b877e4857383c375d5f209dbd288f99
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +15 -0
- package/package.json +1 -1
- package/src/cli-helpers/askForAppSecret.js +9 -4
- package/src/cli-helpers/askForDockerhubAccessToken.js +10 -13
- package/src/cli-helpers/askForDockerhubUsername.js +6 -7
- package/src/cli-helpers/askForWalletAddress.js +7 -5
- package/src/cli-helpers/askForWalletPrivateKey.js +6 -4
- package/src/cli-helpers/askShowResult.js +4 -3
- package/src/cli-helpers/box.js +14 -0
- package/src/cli-helpers/color.js +8 -0
- package/src/cmd/deploy.js +10 -2
- package/src/cmd/init.js +2 -9
- package/src/cmd/mock-protected-data.js +8 -14
- package/src/cmd/run.js +17 -37
- package/src/cmd/test.js +13 -5
- package/src/execDocker/docker.js +7 -10
- package/src/index.js +1 -1
- package/src/utils/initIAppWorkspace.js +3 -0
- package/templates/js/_.gitignore +9 -0
package/README.md
CHANGED
@@ -14,6 +14,21 @@ This CLI provides an interface to guide you through different steps:
|
|
14
14
|
(`iapp init` will also propose you to do so)
|
15
15
|
- Docker
|
16
16
|
|
17
|
+
> ℹ️ For MacOS users
|
18
|
+
>
|
19
|
+
> This tool use `docker buildx` to build images for `linux/amd64` platform
|
20
|
+
> compatible with iExec's decentralized workers.
|
21
|
+
>
|
22
|
+
> Make sure your docker builder supports AMD64 architecture:
|
23
|
+
>
|
24
|
+
> ```sh
|
25
|
+
> docker buildx inspect --bootstrap | grep -i platforms
|
26
|
+
> ```
|
27
|
+
>
|
28
|
+
> The output should include `linux/amd64` in the list of supported platforms. If
|
29
|
+
> not update te the latest Docker Desktop version which includes these
|
30
|
+
> requirements.
|
31
|
+
|
17
32
|
## Install
|
18
33
|
|
19
34
|
```sh
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@iexec/iapp-maker",
|
3
|
-
"version": "0.0.1-alpha-nightly-
|
3
|
+
"version": "0.0.1-alpha-nightly-6be476717b877e4857383c375d5f209dbd288f99",
|
4
4
|
"description": "A CLI to guide you through the process of building an iExec iApp",
|
5
5
|
"main": "src/index.js",
|
6
6
|
"type": "module",
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js';
|
2
2
|
import { CONFIG_FILE } from '../config/config.js';
|
3
|
+
import * as color from './color.js';
|
3
4
|
|
4
5
|
/**
|
5
6
|
* @returns {Promise<string | null>}
|
@@ -9,11 +10,15 @@ export async function askForAppSecret({ spinner }) {
|
|
9
10
|
const { appSecret: savedAppSecret } = config;
|
10
11
|
|
11
12
|
if (savedAppSecret === null) {
|
12
|
-
spinner.log(
|
13
|
+
spinner.log(
|
14
|
+
`"No app secret" is configured ${color.comment(`(from ${color.file(CONFIG_FILE)})`)}`
|
15
|
+
);
|
13
16
|
return savedAppSecret;
|
14
17
|
}
|
15
18
|
if (savedAppSecret !== undefined) {
|
16
|
-
spinner.log(
|
19
|
+
spinner.log(
|
20
|
+
`Using saved appSecret ${color.comment(`(from ${color.file(CONFIG_FILE)})`)}`
|
21
|
+
);
|
17
22
|
return savedAppSecret;
|
18
23
|
}
|
19
24
|
|
@@ -37,7 +42,7 @@ export async function askForAppSecret({ spinner }) {
|
|
37
42
|
if (saveNull) {
|
38
43
|
config.appSecret = null;
|
39
44
|
await writeIAppConfig(config);
|
40
|
-
spinner.log(`"No appSecret" choice saved to
|
45
|
+
spinner.log(`"No appSecret" choice saved to ${color.file(CONFIG_FILE)}`);
|
41
46
|
}
|
42
47
|
return null;
|
43
48
|
}
|
@@ -61,7 +66,7 @@ export async function askForAppSecret({ spinner }) {
|
|
61
66
|
if (saveAppSecret) {
|
62
67
|
config.appSecret = appSecret;
|
63
68
|
await writeIAppConfig(config);
|
64
|
-
spinner.log(`appSecret saved to
|
69
|
+
spinner.log(`appSecret saved to ${color.file(CONFIG_FILE)}`);
|
65
70
|
}
|
66
71
|
|
67
72
|
return appSecret;
|
@@ -1,40 +1,37 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
1
|
import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js';
|
3
2
|
import { CONFIG_FILE } from '../config/config.js';
|
3
|
+
import * as color from './color.js';
|
4
4
|
|
5
5
|
export async function askForDockerhubAccessToken({ spinner }) {
|
6
6
|
const config = await readIAppConfig();
|
7
7
|
|
8
8
|
const dockerhubAccessToken = config.dockerhubAccessToken || '';
|
9
9
|
if (dockerhubAccessToken) {
|
10
|
-
spinner.log(
|
10
|
+
spinner.log(
|
11
|
+
`Using saved dockerhubAccessToken ${color.comment(`(from ${color.file(CONFIG_FILE)})`)}`
|
12
|
+
);
|
11
13
|
return dockerhubAccessToken;
|
12
14
|
}
|
13
15
|
|
14
|
-
spinner.log(
|
15
|
-
'Go to your docker hub account: https://hub.docker.com/settings/security'
|
16
|
-
);
|
17
|
-
spinner.log('click on "Personal access tokens"');
|
18
|
-
spinner.log('click on "Generate new token"');
|
19
|
-
spinner.log('you can name it "Test iExec iApp CLI"');
|
20
|
-
spinner.log('and select "Read & Write" Access permissions');
|
21
16
|
const { dockerHubAccessTokenAnswer } = await spinner.prompt({
|
22
17
|
type: 'password',
|
23
18
|
name: 'dockerHubAccessTokenAnswer',
|
24
|
-
message:
|
25
|
-
|
19
|
+
message: `What is your DockerHub access token?
|
20
|
+
${color.promptHelper(`You need to provide a Personal access token with ${color.emphasis('Read & Write')} access
|
21
|
+
This token will be used to push your iApp docker images to your account
|
22
|
+
You can create a new token by visiting ${color.link('https://app.docker.com/settings/personal-access-tokens/create')}`)}`,
|
26
23
|
});
|
27
24
|
|
28
25
|
// TODO check token against API
|
29
26
|
if (!/[a-zA-Z0-9-]+/.test(dockerHubAccessTokenAnswer)) {
|
30
|
-
spinner.log(
|
27
|
+
spinner.log(color.error('Invalid DockerHub access token.'));
|
31
28
|
return askForDockerhubAccessToken({ spinner });
|
32
29
|
}
|
33
30
|
|
34
31
|
// Save it into JSON config file
|
35
32
|
config.dockerhubAccessToken = dockerHubAccessTokenAnswer;
|
36
33
|
await writeIAppConfig(config);
|
37
|
-
spinner.log(`dockerhubAccessToken saved to
|
34
|
+
spinner.log(`dockerhubAccessToken saved to ${color.file(CONFIG_FILE)}`);
|
38
35
|
|
39
36
|
return dockerHubAccessTokenAnswer;
|
40
37
|
}
|
@@ -1,6 +1,6 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
1
|
import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js';
|
3
2
|
import { CONFIG_FILE } from '../config/config.js';
|
3
|
+
import * as color from './color.js';
|
4
4
|
|
5
5
|
export async function askForDockerhubUsername({ spinner }) {
|
6
6
|
const config = await readIAppConfig();
|
@@ -8,7 +8,7 @@ export async function askForDockerhubUsername({ spinner }) {
|
|
8
8
|
const dockerhubUsername = config.dockerhubUsername || '';
|
9
9
|
if (dockerhubUsername) {
|
10
10
|
spinner.log(
|
11
|
-
`Using saved dockerhubUsername (from
|
11
|
+
`Using saved dockerhubUsername ${color.comment(`(from ${color.file(CONFIG_FILE)})`)} -> ${dockerhubUsername}`
|
12
12
|
);
|
13
13
|
return dockerhubUsername;
|
14
14
|
}
|
@@ -16,15 +16,14 @@ export async function askForDockerhubUsername({ spinner }) {
|
|
16
16
|
const { dockerHubUserNameAnswer } = await spinner.prompt({
|
17
17
|
type: 'text',
|
18
18
|
name: 'dockerHubUserNameAnswer',
|
19
|
-
message:
|
20
|
-
'What is your username on DockerHub? (It will be used to properly tag the Docker image)',
|
19
|
+
message: `What is your username on DockerHub? ${color.promptHelper('(It will be used to properly tag the Docker image)')}`,
|
21
20
|
});
|
22
21
|
|
23
22
|
// TODO check username against API
|
24
23
|
if (!/[a-zA-Z0-9-]+/.test(dockerHubUserNameAnswer)) {
|
25
24
|
spinner.log(
|
26
|
-
|
27
|
-
|
25
|
+
color.error(
|
26
|
+
`Invalid DockerHub username. Login to ${color.link('https://hub.docker.com')} to check your username.`
|
28
27
|
)
|
29
28
|
);
|
30
29
|
return askForDockerhubUsername({ spinner });
|
@@ -33,7 +32,7 @@ export async function askForDockerhubUsername({ spinner }) {
|
|
33
32
|
// Save it into JSON config file
|
34
33
|
config.dockerhubUsername = dockerHubUserNameAnswer;
|
35
34
|
await writeIAppConfig(config);
|
36
|
-
spinner.log(`dockerhubUsername saved to
|
35
|
+
spinner.log(`dockerhubUsername saved to ${color.file(CONFIG_FILE)}`);
|
37
36
|
|
38
37
|
return dockerHubUserNameAnswer;
|
39
38
|
}
|
@@ -1,13 +1,15 @@
|
|
1
|
-
import
|
1
|
+
import { isAddress } from 'ethers';
|
2
2
|
import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js';
|
3
3
|
import { CONFIG_FILE } from '../config/config.js';
|
4
|
-
import
|
4
|
+
import * as color from './color.js';
|
5
5
|
|
6
6
|
export async function askForWalletAddress({ spinner }) {
|
7
7
|
const config = await readIAppConfig();
|
8
8
|
const walletAddress = config.walletAddress || '';
|
9
9
|
if (walletAddress) {
|
10
|
-
spinner.log(
|
10
|
+
spinner.log(
|
11
|
+
`Using saved walletAddress ${color.comment(`(from ${color.file(CONFIG_FILE)})`)}`
|
12
|
+
);
|
11
13
|
return walletAddress;
|
12
14
|
}
|
13
15
|
|
@@ -19,7 +21,7 @@ export async function askForWalletAddress({ spinner }) {
|
|
19
21
|
|
20
22
|
if (!isAddress(walletAddressAnswer)) {
|
21
23
|
spinner.log(
|
22
|
-
|
24
|
+
color.error(
|
23
25
|
'Invalid wallet address. Ex: 0xC248cCe0a656a90F2Ae27ccfa8Bd11843c8e0f3c'
|
24
26
|
)
|
25
27
|
);
|
@@ -29,7 +31,7 @@ export async function askForWalletAddress({ spinner }) {
|
|
29
31
|
// Save it into JSON config file
|
30
32
|
config.walletAddress = walletAddressAnswer;
|
31
33
|
await writeIAppConfig(config);
|
32
|
-
spinner.log(`walletAddress saved to
|
34
|
+
spinner.log(`walletAddress saved to ${color.file(CONFIG_FILE)}`);
|
33
35
|
|
34
36
|
return walletAddressAnswer;
|
35
37
|
}
|
@@ -1,14 +1,16 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
1
|
import { Wallet } from 'ethers';
|
3
2
|
import { readIAppConfig, writeIAppConfig } from '../utils/iAppConfigFile.js';
|
4
3
|
import { CONFIG_FILE } from '../config/config.js';
|
4
|
+
import * as color from './color.js';
|
5
5
|
|
6
6
|
export async function askForWalletPrivateKey({ spinner }) {
|
7
7
|
const config = await readIAppConfig();
|
8
8
|
|
9
9
|
const walletPrivateKey = config.walletPrivateKey || '';
|
10
10
|
if (walletPrivateKey) {
|
11
|
-
spinner.log(
|
11
|
+
spinner.log(
|
12
|
+
`Using saved walletPrivateKey ${color.comment(`(from ${color.file(CONFIG_FILE)})`)}`
|
13
|
+
);
|
12
14
|
return walletPrivateKey;
|
13
15
|
}
|
14
16
|
|
@@ -22,7 +24,7 @@ export async function askForWalletPrivateKey({ spinner }) {
|
|
22
24
|
try {
|
23
25
|
new Wallet(walletPrivateKeyAnswer);
|
24
26
|
} catch {
|
25
|
-
spinner.log(
|
27
|
+
spinner.log(color.error('Invalid wallet private key'));
|
26
28
|
return askForWalletPrivateKey({ spinner });
|
27
29
|
}
|
28
30
|
|
@@ -41,7 +43,7 @@ export async function askForWalletPrivateKey({ spinner }) {
|
|
41
43
|
|
42
44
|
config.walletPrivateKey = walletPrivateKeyAnswer;
|
43
45
|
await writeIAppConfig(config);
|
44
|
-
spinner.log(`walletPrivateKey saved to
|
46
|
+
spinner.log(`walletPrivateKey saved to ${color.file(CONFIG_FILE)}`);
|
45
47
|
|
46
48
|
return walletPrivateKeyAnswer;
|
47
49
|
}
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import { readdir } from 'node:fs/promises';
|
2
2
|
import { getDeterministicOutputAsText } from '../utils/deterministicOutput.js';
|
3
|
+
import * as color from './color.js';
|
3
4
|
|
4
5
|
export async function askShowResult({ spinner, outputPath }) {
|
5
6
|
// Prompt user to view result
|
6
7
|
const continueAnswer = await spinner.prompt({
|
7
8
|
type: 'confirm',
|
8
9
|
name: 'continue',
|
9
|
-
message: `Would you like to see the result? (View
|
10
|
+
message: `Would you like to see the result? ${color.promptHelper(`(View ${color.file(`./${outputPath}/`)})`)}`,
|
10
11
|
initial: true,
|
11
12
|
});
|
12
13
|
if (continueAnswer.continue) {
|
@@ -16,13 +17,13 @@ export async function askShowResult({ spinner, outputPath }) {
|
|
16
17
|
spinner.warn('output directory is empty');
|
17
18
|
} else {
|
18
19
|
spinner.info(
|
19
|
-
`output directory content:\n${files.map((file) => ' - ' + file).join('\n')}`
|
20
|
+
`output directory content:\n${files.map((file) => ' - ' + color.file(file)).join('\n')}`
|
20
21
|
);
|
21
22
|
// best effort display deterministic output file if it's an utf8 encoded file
|
22
23
|
await getDeterministicOutputAsText({ outputPath })
|
23
24
|
.then(({ text, path }) => {
|
24
25
|
spinner.newLine();
|
25
|
-
spinner.info(`${path}:\n${text}`);
|
26
|
+
spinner.info(`${color.file(path)}:\n${text}`);
|
26
27
|
})
|
27
28
|
.catch(() => {});
|
28
29
|
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import boxen from 'boxen';
|
2
|
+
|
3
|
+
export function hintBox(message) {
|
4
|
+
return boxen(message, {
|
5
|
+
padding: 1,
|
6
|
+
margin: 1,
|
7
|
+
borderStyle: 'round',
|
8
|
+
borderColor: 'cyan',
|
9
|
+
});
|
10
|
+
}
|
11
|
+
|
12
|
+
export function objectBox(message) {
|
13
|
+
return boxen(message, { margin: 1 });
|
14
|
+
}
|
package/src/cli-helpers/color.js
CHANGED
@@ -20,3 +20,11 @@ export const emphasis = chalk.green;
|
|
20
20
|
* use to color file name
|
21
21
|
*/
|
22
22
|
export const file = chalk.cyan;
|
23
|
+
/**
|
24
|
+
* use to color web links
|
25
|
+
*/
|
26
|
+
export const link = chalk.blue.underline;
|
27
|
+
/**
|
28
|
+
* use to color errors
|
29
|
+
*/
|
30
|
+
export const error = chalk.red;
|
package/src/cmd/deploy.js
CHANGED
@@ -15,6 +15,8 @@ import { askForWalletPrivateKey } from '../cli-helpers/askForWalletPrivateKey.js
|
|
15
15
|
import { Wallet } from 'ethers';
|
16
16
|
import { getIExecDebug } from '../utils/iexec.js';
|
17
17
|
import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js';
|
18
|
+
import * as color from '../cli-helpers/color.js';
|
19
|
+
import { hintBox } from '../cli-helpers/box.js';
|
18
20
|
|
19
21
|
export async function deploy() {
|
20
22
|
const spinner = getSpinner();
|
@@ -67,7 +69,7 @@ export async function deploy() {
|
|
67
69
|
tag: imageTag,
|
68
70
|
progressCallback: (msg) => {
|
69
71
|
buildLogs.push(msg); // do we want to show build logs after build is successful?
|
70
|
-
spinner.text = spinner.text + msg;
|
72
|
+
spinner.text = spinner.text + color.comment(msg);
|
71
73
|
},
|
72
74
|
});
|
73
75
|
spinner.succeed(`Docker image built (${imageId}) and tagged ${imageTag}`);
|
@@ -78,7 +80,7 @@ export async function deploy() {
|
|
78
80
|
dockerhubAccessToken,
|
79
81
|
dockerhubUsername,
|
80
82
|
progressCallback: (msg) => {
|
81
|
-
spinner.text = spinner.text + msg;
|
83
|
+
spinner.text = spinner.text + color.comment(msg);
|
82
84
|
},
|
83
85
|
});
|
84
86
|
spinner.succeed(`Pushed image ${imageTag} on dockerhub`);
|
@@ -104,6 +106,12 @@ export async function deploy() {
|
|
104
106
|
- Docker image: ${sconifiedImage}
|
105
107
|
- iApp address: ${appContractAddress}`
|
106
108
|
);
|
109
|
+
|
110
|
+
spinner.log(
|
111
|
+
hintBox(
|
112
|
+
`Run ${color.command(`iapp run ${appContractAddress}`)} to execute your iApp on an iExec TEE worker`
|
113
|
+
)
|
114
|
+
);
|
107
115
|
} catch (error) {
|
108
116
|
handleCliError({ spinner, error });
|
109
117
|
}
|
package/src/cmd/init.js
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import chalk from 'chalk';
|
2
|
-
import boxen from 'boxen';
|
3
2
|
import figlet from 'figlet';
|
4
3
|
import { mkdir } from 'node:fs/promises';
|
5
4
|
import { folderExists } from '../utils/fs.utils.js';
|
@@ -8,6 +7,7 @@ import { getSpinner } from '../cli-helpers/spinner.js';
|
|
8
7
|
import { handleCliError } from '../cli-helpers/handleCliError.js';
|
9
8
|
import { generateWallet } from '../utils/generateWallet.js';
|
10
9
|
import * as color from '../cli-helpers/color.js';
|
10
|
+
import { hintBox } from '../cli-helpers/box.js';
|
11
11
|
|
12
12
|
const targetDir = 'hello-world';
|
13
13
|
|
@@ -170,14 +170,7 @@ export async function init() {
|
|
170
170
|
${color.command('$ iapp run <iapp-address>')}
|
171
171
|
`;
|
172
172
|
|
173
|
-
spinner.log(
|
174
|
-
boxen(output, {
|
175
|
-
padding: 1,
|
176
|
-
margin: 1,
|
177
|
-
borderStyle: 'round',
|
178
|
-
borderColor: 'cyan',
|
179
|
-
})
|
180
|
-
);
|
173
|
+
spinner.log(hintBox(output));
|
181
174
|
} catch (error) {
|
182
175
|
handleCliError({ spinner, error });
|
183
176
|
}
|
@@ -1,7 +1,5 @@
|
|
1
1
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
|
2
2
|
import { join } from 'node:path';
|
3
|
-
import chalk from 'chalk';
|
4
|
-
import boxen from 'boxen';
|
5
3
|
import { getSpinner } from '../cli-helpers/spinner.js';
|
6
4
|
import { fileExists } from '../utils/fs.utils.js';
|
7
5
|
import { PROTECTED_DATA_MOCK_DIR } from '../config/config.js';
|
@@ -12,6 +10,8 @@ import {
|
|
12
10
|
ALLOWED_KEY_NAMES_REGEXP,
|
13
11
|
} from '../libs/dataprotector.js';
|
14
12
|
import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js';
|
13
|
+
import * as color from '../cli-helpers/color.js';
|
14
|
+
import { hintBox, objectBox } from '../cli-helpers/box.js';
|
15
15
|
|
16
16
|
export async function mockProtectedData() {
|
17
17
|
const spinner = getSpinner();
|
@@ -163,7 +163,7 @@ export async function mockProtectedData() {
|
|
163
163
|
|
164
164
|
spinner.info(
|
165
165
|
`This is how your protectedData looks so far:
|
166
|
-
${
|
166
|
+
${objectBox(JSON.stringify(dataSchema, null, 2))}`
|
167
167
|
);
|
168
168
|
|
169
169
|
const { addMore } = await spinner.prompt([
|
@@ -196,7 +196,7 @@ ${boxen(JSON.stringify(dataSchema, null, 2), { margin: 1 })}`
|
|
196
196
|
}
|
197
197
|
|
198
198
|
spinner.start(
|
199
|
-
`Creating protectedData mock file in
|
199
|
+
`Creating protectedData mock file in ${color.file(PROTECTED_DATA_MOCK_DIR)} directory...`
|
200
200
|
);
|
201
201
|
|
202
202
|
const unencryptedData = await createZipFromObject(data);
|
@@ -204,21 +204,15 @@ ${boxen(JSON.stringify(dataSchema, null, 2), { margin: 1 })}`
|
|
204
204
|
await mkdir(PROTECTED_DATA_MOCK_DIR, { recursive: true });
|
205
205
|
await writeFile(join(PROTECTED_DATA_MOCK_DIR, mockName), unencryptedData);
|
206
206
|
spinner.succeed(
|
207
|
-
`Mocked protectedData
|
207
|
+
`Mocked protectedData ${color.file(mockName)} created in ${color.file(PROTECTED_DATA_MOCK_DIR)} directory`
|
208
208
|
);
|
209
209
|
spinner.log(
|
210
|
-
|
210
|
+
hintBox(
|
211
211
|
`protectedData mock "${mockName}" schema:
|
212
|
-
${
|
212
|
+
${color.command(objectBox(JSON.stringify(schema, null, 2)))}
|
213
213
|
|
214
214
|
Use your mock in tests:
|
215
|
-
${
|
216
|
-
{
|
217
|
-
padding: 1,
|
218
|
-
margin: 1,
|
219
|
-
borderStyle: 'round',
|
220
|
-
borderColor: 'cyan',
|
221
|
-
}
|
215
|
+
${color.command(`iapp test --protectedData "${mockName}"`)}`
|
222
216
|
)
|
223
217
|
);
|
224
218
|
} catch (error) {
|
package/src/cmd/run.js
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import chalk from 'chalk';
|
2
1
|
import { v4 as uuidV4 } from 'uuid';
|
3
2
|
import { ethers } from 'ethers';
|
4
3
|
import { mkdir, rm } from 'node:fs/promises';
|
@@ -16,6 +15,7 @@ import { getIExecDebug } from '../utils/iexec.js';
|
|
16
15
|
import { extractZipToFolder } from '../utils/extractZipToFolder.js';
|
17
16
|
import { askShowResult } from '../cli-helpers/askShowResult.js';
|
18
17
|
import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js';
|
18
|
+
import * as color from '../cli-helpers/color.js';
|
19
19
|
|
20
20
|
export async function run({
|
21
21
|
iAppAddress,
|
@@ -51,23 +51,17 @@ export async function runInDebug({
|
|
51
51
|
}) {
|
52
52
|
// Is valid iApp address
|
53
53
|
if (!ethers.isAddress(iAppAddress)) {
|
54
|
-
|
55
|
-
|
56
|
-
'The iApp address is invalid. Be careful ENS name is not implemented yet ...'
|
57
|
-
)
|
54
|
+
throw Error(
|
55
|
+
'The iApp address is invalid. Be careful ENS name is not implemented yet ...'
|
58
56
|
);
|
59
|
-
return;
|
60
57
|
}
|
61
58
|
|
62
59
|
if (protectedData) {
|
63
60
|
// Is valid protectedData address
|
64
61
|
if (!ethers.isAddress(protectedData)) {
|
65
|
-
|
66
|
-
|
67
|
-
'The protectedData address is invalid. Be careful ENS name is not implemented yet ...'
|
68
|
-
)
|
62
|
+
throw Error(
|
63
|
+
'The protectedData address is invalid. Be careful ENS name is not implemented yet ...'
|
69
64
|
);
|
70
|
-
return;
|
71
65
|
}
|
72
66
|
}
|
73
67
|
|
@@ -89,18 +83,13 @@ export async function runInDebug({
|
|
89
83
|
);
|
90
84
|
|
91
85
|
if (!isSecretSet) {
|
92
|
-
|
93
|
-
|
94
|
-
`Your protectedData secret key is not registered in the debug secret management service (SMS) of iexec protocol`
|
95
|
-
)
|
86
|
+
throw Error(
|
87
|
+
`Your protectedData secret key is not registered in the debug secret management service (SMS) of iexec protocol`
|
96
88
|
);
|
97
|
-
return;
|
98
89
|
}
|
99
90
|
} catch (e) {
|
100
|
-
|
101
|
-
|
102
|
-
`Error while running your iApp with your protectedData: ${e.message}`
|
103
|
-
)
|
91
|
+
throw Error(
|
92
|
+
`Error while running your iApp with your protectedData: ${e.message}`
|
104
93
|
);
|
105
94
|
}
|
106
95
|
}
|
@@ -130,9 +119,9 @@ export async function runInDebug({
|
|
130
119
|
});
|
131
120
|
const workerpoolorder = workerpoolOrderbook.orders[0]?.order;
|
132
121
|
if (!workerpoolorder) {
|
133
|
-
|
134
|
-
|
135
|
-
|
122
|
+
throw Error(
|
123
|
+
'No WorkerpoolOrder found, Wait until some workerpoolOrder come back'
|
124
|
+
);
|
136
125
|
}
|
137
126
|
spinner.succeed('Workerpool order fetched');
|
138
127
|
|
@@ -163,13 +152,9 @@ export async function runInDebug({
|
|
163
152
|
);
|
164
153
|
datasetorder = datasetOrderbook.orders[0]?.order;
|
165
154
|
if (!datasetorder) {
|
166
|
-
|
167
|
-
|
168
|
-
chalk.red(
|
169
|
-
'It seems your iApp is not allowed to access the protectedData, please grantAccess to it'
|
170
|
-
)
|
155
|
+
throw Error(
|
156
|
+
'No matching ProtectedData access found, It seems your iApp is not allowed to access the protectedData, please grantAccess to it'
|
171
157
|
);
|
172
|
-
return;
|
173
158
|
}
|
174
159
|
spinner.succeed('ProtectedData access found');
|
175
160
|
}
|
@@ -223,14 +208,9 @@ export async function runInDebug({
|
|
223
208
|
clearTimeout(taskTimeoutWarning);
|
224
209
|
});
|
225
210
|
|
226
|
-
spinner.succeed('Task finalized');
|
227
|
-
|
228
211
|
const task = await iexec.task.show(taskId);
|
229
|
-
spinner.
|
230
|
-
|
231
|
-
`You can download the result of your task here: https://ipfs-gateway.v8-bellecour.iex.ec${task?.results?.location}`
|
232
|
-
)
|
233
|
-
);
|
212
|
+
spinner.succeed(`Task finalized
|
213
|
+
You can download the result of your task here: ${color.link(`https://ipfs-gateway.v8-bellecour.iex.ec${task?.results?.location}`)}`);
|
234
214
|
|
235
215
|
const downloadAnswer = await spinner.prompt({
|
236
216
|
type: 'confirm',
|
@@ -248,7 +228,7 @@ export async function runInDebug({
|
|
248
228
|
const taskResult = await iexec.task.fetchResults(taskId);
|
249
229
|
const resultBuffer = await taskResult.arrayBuffer();
|
250
230
|
await extractZipToFolder(resultBuffer, outputFolder);
|
251
|
-
spinner.succeed(`Result downloaded to ${outputFolder}`);
|
231
|
+
spinner.succeed(`Result downloaded to ${color.file(outputFolder)}`);
|
252
232
|
|
253
233
|
await askShowResult({ spinner, outputPath: outputFolder });
|
254
234
|
}
|
package/src/cmd/test.js
CHANGED
@@ -23,6 +23,8 @@ import { askForAppSecret } from '../cli-helpers/askForAppSecret.js';
|
|
23
23
|
import { askShowResult } from '../cli-helpers/askShowResult.js';
|
24
24
|
import { copy, fileExists } from '../utils/fs.utils.js';
|
25
25
|
import { goToProjectRoot } from '../cli-helpers/goToProjectRoot.js';
|
26
|
+
import * as color from '../cli-helpers/color.js';
|
27
|
+
import { hintBox } from '../cli-helpers/box.js';
|
26
28
|
|
27
29
|
export async function test({
|
28
30
|
args,
|
@@ -47,6 +49,12 @@ export async function test({
|
|
47
49
|
});
|
48
50
|
await checkTestOutput({ spinner });
|
49
51
|
await askShowResult({ spinner, outputPath: TEST_OUTPUT_DIR });
|
52
|
+
// TODO: check test warnings and errors and adapt the message
|
53
|
+
spinner.log(
|
54
|
+
hintBox(
|
55
|
+
`When ready run ${color.command(`iapp deploy`)} to transform you app into a TEE app and deploy it on iExec`
|
56
|
+
)
|
57
|
+
);
|
50
58
|
} catch (error) {
|
51
59
|
handleCliError({ spinner, error });
|
52
60
|
}
|
@@ -105,7 +113,7 @@ export async function testApp({
|
|
105
113
|
const imageId = await dockerBuild({
|
106
114
|
isForTest: true,
|
107
115
|
progressCallback: (msg) => {
|
108
|
-
spinner.text = spinner.text + msg;
|
116
|
+
spinner.text = spinner.text + color.comment(msg);
|
109
117
|
},
|
110
118
|
});
|
111
119
|
spinner.succeed(`App docker image built (${imageId})`);
|
@@ -129,7 +137,7 @@ export async function testApp({
|
|
129
137
|
const mockExists = await fileExists(protectedDataMockPath);
|
130
138
|
if (!mockExists) {
|
131
139
|
throw Error(
|
132
|
-
`No protectedData mock "${protectedDataMock}" found in ${PROTECTED_DATA_MOCK_DIR}, run
|
140
|
+
`No protectedData mock "${protectedDataMock}" found in ${PROTECTED_DATA_MOCK_DIR}, run ${color.command('iapp mock protectedData')} to create a new protectedData mock`
|
133
141
|
);
|
134
142
|
}
|
135
143
|
await copy(
|
@@ -187,7 +195,7 @@ export async function testApp({
|
|
187
195
|
memory: IEXEC_WORKER_HEAP_SIZE,
|
188
196
|
logsCallback: (msg) => {
|
189
197
|
appLogs.push(msg); // collect logs for future use
|
190
|
-
spinner.text = spinner.text + msg; // and display realtime while app is running
|
198
|
+
spinner.text = spinner.text + color.comment(msg); // and display realtime while app is running
|
191
199
|
},
|
192
200
|
}).finally(() => {
|
193
201
|
clearTimeout(taskTimeoutWarning);
|
@@ -195,7 +203,7 @@ export async function testApp({
|
|
195
203
|
if (outOfMemory) {
|
196
204
|
spinner.fail(
|
197
205
|
`App docker image container ran out of memory.
|
198
|
-
iExec worker's ${Math.floor(IEXEC_WORKER_HEAP_SIZE / (1024 * 1024))}
|
206
|
+
iExec worker's ${Math.floor(IEXEC_WORKER_HEAP_SIZE / (1024 * 1024))}MiB memory limit exceeded.
|
199
207
|
You must refactor your app to run within the memory limit.`
|
200
208
|
);
|
201
209
|
} else if (exitCode === 0) {
|
@@ -213,7 +221,7 @@ export async function testApp({
|
|
213
221
|
const showLogs = await spinner.prompt({
|
214
222
|
type: 'confirm',
|
215
223
|
name: 'continue',
|
216
|
-
message: `Would you like to see the app logs? (${appLogs.length} lines)`,
|
224
|
+
message: `Would you like to see the app logs? ${color.promptHelper(`(${appLogs.length} lines)`)}`,
|
217
225
|
initial: false,
|
218
226
|
});
|
219
227
|
if (showLogs.continue) {
|
package/src/execDocker/docker.js
CHANGED
@@ -24,22 +24,19 @@ export async function dockerBuild({
|
|
24
24
|
src: ['./'],
|
25
25
|
};
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
} else {
|
34
|
-
// AMD64 variant to deploy to iExec stack
|
35
|
-
platform = 'linux/amd64';
|
36
|
-
}
|
27
|
+
// by default force to build amd64 image which is architecture used in iExec workers
|
28
|
+
// this require buildx builder to support 'linux/amd64' (some devices may need QEMU for amd64 architecture emulation)
|
29
|
+
let platform = 'linux/amd64';
|
30
|
+
// for MacOS local testing only build arm64 variant
|
31
|
+
if (osType === 'Darwin' && isForTest) {
|
32
|
+
platform = 'linux/arm64';
|
37
33
|
}
|
38
34
|
|
39
35
|
// Perform the Docker build operation
|
40
36
|
const buildImageStream = await docker.buildImage(buildArgs, {
|
41
37
|
t: tag,
|
42
38
|
platform,
|
39
|
+
pull: true, // docker store does not support multi platform image, this can cause issues when switching build target platform, pulling ensures the right image is used
|
43
40
|
});
|
44
41
|
|
45
42
|
const imageId = await new Promise((resolve, reject) => {
|
package/src/index.js
CHANGED
@@ -91,6 +91,9 @@ async function copyChosenTemplateFiles({
|
|
91
91
|
const files = await fs.readdir(templateDir);
|
92
92
|
await Promise.all(files.map((file) => write(file)));
|
93
93
|
|
94
|
+
// rename _.gitignore (npm does not allow publishing files named .gitignore in a package)
|
95
|
+
await fs.rename('_.gitignore', '.gitignore');
|
96
|
+
|
94
97
|
// transform template: remove unwanted feature code inside " // <<feature>> ... // <</feature>>" tags
|
95
98
|
const code = (await fs.readFile(srcFile)).toString('utf8');
|
96
99
|
let modifiedCode = code;
|