mobilestacks 0.1.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/Clarinet.toml +7 -0
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/contracts/sample-contract.clar +2 -0
- package/dist/mobilestacks.config.d.ts +20 -0
- package/dist/mobilestacks.config.d.ts.map +1 -0
- package/dist/mobilestacks.config.js +23 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +102 -0
- package/dist/src/cli/init.d.ts +2 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +55 -0
- package/dist/src/config/config-loading.d.ts +3 -0
- package/dist/src/config/config-loading.d.ts.map +1 -0
- package/dist/src/config/config-loading.js +29 -0
- package/dist/src/core/dsl.d.ts +30 -0
- package/dist/src/core/dsl.d.ts.map +1 -0
- package/dist/src/core/dsl.js +62 -0
- package/dist/src/core/dsl.test.d.ts +2 -0
- package/dist/src/core/dsl.test.d.ts.map +1 -0
- package/dist/src/core/dsl.test.js +48 -0
- package/dist/src/core/env.d.ts +6 -0
- package/dist/src/core/env.d.ts.map +1 -0
- package/dist/src/core/env.js +7 -0
- package/dist/src/core/extender.d.ts +12 -0
- package/dist/src/core/extender.d.ts.map +1 -0
- package/dist/src/core/extender.js +20 -0
- package/dist/src/core/runtime-environment.d.ts +15 -0
- package/dist/src/core/runtime-environment.d.ts.map +1 -0
- package/dist/src/core/runtime-environment.js +65 -0
- package/dist/src/core/simnet.d.ts +17 -0
- package/dist/src/core/simnet.d.ts.map +1 -0
- package/dist/src/core/simnet.js +42 -0
- package/dist/src/core/tasks-definitions.d.ts +28 -0
- package/dist/src/core/tasks-definitions.d.ts.map +1 -0
- package/dist/src/core/tasks-definitions.js +21 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/tasks/call-contract-function.d.ts +2 -0
- package/dist/src/tasks/call-contract-function.d.ts.map +1 -0
- package/dist/src/tasks/call-contract-function.js +31 -0
- package/dist/src/tasks/deploy-contract.d.ts +2 -0
- package/dist/src/tasks/deploy-contract.d.ts.map +1 -0
- package/dist/src/tasks/deploy-contract.js +44 -0
- package/dist/src/tasks/example-task.d.ts +2 -0
- package/dist/src/tasks/example-task.d.ts.map +1 -0
- package/dist/src/tasks/example-task.js +6 -0
- package/dist/src/tasks/faucet-request.d.ts +2 -0
- package/dist/src/tasks/faucet-request.d.ts.map +1 -0
- package/dist/src/tasks/faucet-request.js +21 -0
- package/dist/src/tasks/get-balance.d.ts +2 -0
- package/dist/src/tasks/get-balance.d.ts.map +1 -0
- package/dist/src/tasks/get-balance.js +33 -0
- package/dist/src/tasks/get-contract-info.d.ts +2 -0
- package/dist/src/tasks/get-contract-info.d.ts.map +1 -0
- package/dist/src/tasks/get-contract-info.js +18 -0
- package/dist/src/tasks/get-tx-history.d.ts +2 -0
- package/dist/src/tasks/get-tx-history.d.ts.map +1 -0
- package/dist/src/tasks/get-tx-history.js +38 -0
- package/dist/src/tasks/list-accounts.d.ts +2 -0
- package/dist/src/tasks/list-accounts.d.ts.map +1 -0
- package/dist/src/tasks/list-accounts.js +24 -0
- package/dist/src/tasks/send-stx.d.ts +2 -0
- package/dist/src/tasks/send-stx.d.ts.map +1 -0
- package/dist/src/tasks/send-stx.js +36 -0
- package/dist/src/tasks/verify-contract.d.ts +2 -0
- package/dist/src/tasks/verify-contract.d.ts.map +1 -0
- package/dist/src/tasks/verify-contract.js +33 -0
- package/dist/src/types/config.d.ts +120 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +19 -0
- package/package.json +70 -0
- package/src/cli/index.ts +105 -0
- package/src/cli/init.ts +57 -0
- package/src/config/config-loading.ts +31 -0
- package/src/core/dsl.test.ts +63 -0
- package/src/core/dsl.ts +73 -0
- package/src/core/env.ts +8 -0
- package/src/core/extender.ts +29 -0
- package/src/core/runtime-environment.ts +74 -0
- package/src/core/simnet.ts +56 -0
- package/src/core/tasks-definitions.ts +47 -0
- package/src/index.ts +4 -0
- package/src/tasks/call-contract-function.ts +31 -0
- package/src/tasks/deploy-contract.ts +49 -0
- package/src/tasks/example-task.ts +7 -0
- package/src/tasks/faucet-request.ts +21 -0
- package/src/tasks/get-balance.ts +34 -0
- package/src/tasks/get-contract-info.ts +18 -0
- package/src/tasks/get-tx-history.ts +39 -0
- package/src/tasks/list-accounts.ts +27 -0
- package/src/tasks/send-stx.ts +39 -0
- package/src/tasks/verify-contract.ts +33 -0
- package/src/types/config.ts +30 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const NetworkConfigSchema: z.ZodObject<{
|
|
3
|
+
url: z.ZodString;
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
explorerUrl: z.ZodOptional<z.ZodString>;
|
|
6
|
+
faucetUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
url: string;
|
|
9
|
+
name: string;
|
|
10
|
+
explorerUrl?: string | undefined;
|
|
11
|
+
faucetUrl?: string | null | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
url: string;
|
|
14
|
+
name: string;
|
|
15
|
+
explorerUrl?: string | undefined;
|
|
16
|
+
faucetUrl?: string | null | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const WalletConfigSchema: z.ZodEffects<z.ZodObject<{
|
|
19
|
+
privateKey: z.ZodOptional<z.ZodString>;
|
|
20
|
+
seedPhrase: z.ZodOptional<z.ZodString>;
|
|
21
|
+
derivationPath: z.ZodOptional<z.ZodString>;
|
|
22
|
+
address: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
privateKey?: string | undefined;
|
|
25
|
+
seedPhrase?: string | undefined;
|
|
26
|
+
derivationPath?: string | undefined;
|
|
27
|
+
address?: string | undefined;
|
|
28
|
+
}, {
|
|
29
|
+
privateKey?: string | undefined;
|
|
30
|
+
seedPhrase?: string | undefined;
|
|
31
|
+
derivationPath?: string | undefined;
|
|
32
|
+
address?: string | undefined;
|
|
33
|
+
}>, {
|
|
34
|
+
privateKey?: string | undefined;
|
|
35
|
+
seedPhrase?: string | undefined;
|
|
36
|
+
derivationPath?: string | undefined;
|
|
37
|
+
address?: string | undefined;
|
|
38
|
+
}, {
|
|
39
|
+
privateKey?: string | undefined;
|
|
40
|
+
seedPhrase?: string | undefined;
|
|
41
|
+
derivationPath?: string | undefined;
|
|
42
|
+
address?: string | undefined;
|
|
43
|
+
}>;
|
|
44
|
+
export declare const MobilestacksConfigSchema: z.ZodObject<{
|
|
45
|
+
networks: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
46
|
+
url: z.ZodString;
|
|
47
|
+
name: z.ZodString;
|
|
48
|
+
explorerUrl: z.ZodOptional<z.ZodString>;
|
|
49
|
+
faucetUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
50
|
+
}, "strip", z.ZodTypeAny, {
|
|
51
|
+
url: string;
|
|
52
|
+
name: string;
|
|
53
|
+
explorerUrl?: string | undefined;
|
|
54
|
+
faucetUrl?: string | null | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
url: string;
|
|
57
|
+
name: string;
|
|
58
|
+
explorerUrl?: string | undefined;
|
|
59
|
+
faucetUrl?: string | null | undefined;
|
|
60
|
+
}>>;
|
|
61
|
+
defaultNetwork: z.ZodString;
|
|
62
|
+
wallet: z.ZodEffects<z.ZodObject<{
|
|
63
|
+
privateKey: z.ZodOptional<z.ZodString>;
|
|
64
|
+
seedPhrase: z.ZodOptional<z.ZodString>;
|
|
65
|
+
derivationPath: z.ZodOptional<z.ZodString>;
|
|
66
|
+
address: z.ZodOptional<z.ZodString>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
privateKey?: string | undefined;
|
|
69
|
+
seedPhrase?: string | undefined;
|
|
70
|
+
derivationPath?: string | undefined;
|
|
71
|
+
address?: string | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
privateKey?: string | undefined;
|
|
74
|
+
seedPhrase?: string | undefined;
|
|
75
|
+
derivationPath?: string | undefined;
|
|
76
|
+
address?: string | undefined;
|
|
77
|
+
}>, {
|
|
78
|
+
privateKey?: string | undefined;
|
|
79
|
+
seedPhrase?: string | undefined;
|
|
80
|
+
derivationPath?: string | undefined;
|
|
81
|
+
address?: string | undefined;
|
|
82
|
+
}, {
|
|
83
|
+
privateKey?: string | undefined;
|
|
84
|
+
seedPhrase?: string | undefined;
|
|
85
|
+
derivationPath?: string | undefined;
|
|
86
|
+
address?: string | undefined;
|
|
87
|
+
}>;
|
|
88
|
+
}, "strip", z.ZodTypeAny, {
|
|
89
|
+
networks: Record<string, {
|
|
90
|
+
url: string;
|
|
91
|
+
name: string;
|
|
92
|
+
explorerUrl?: string | undefined;
|
|
93
|
+
faucetUrl?: string | null | undefined;
|
|
94
|
+
}>;
|
|
95
|
+
defaultNetwork: string;
|
|
96
|
+
wallet: {
|
|
97
|
+
privateKey?: string | undefined;
|
|
98
|
+
seedPhrase?: string | undefined;
|
|
99
|
+
derivationPath?: string | undefined;
|
|
100
|
+
address?: string | undefined;
|
|
101
|
+
};
|
|
102
|
+
}, {
|
|
103
|
+
networks: Record<string, {
|
|
104
|
+
url: string;
|
|
105
|
+
name: string;
|
|
106
|
+
explorerUrl?: string | undefined;
|
|
107
|
+
faucetUrl?: string | null | undefined;
|
|
108
|
+
}>;
|
|
109
|
+
defaultNetwork: string;
|
|
110
|
+
wallet: {
|
|
111
|
+
privateKey?: string | undefined;
|
|
112
|
+
seedPhrase?: string | undefined;
|
|
113
|
+
derivationPath?: string | undefined;
|
|
114
|
+
address?: string | undefined;
|
|
115
|
+
};
|
|
116
|
+
}>;
|
|
117
|
+
export type NetworkConfig = z.infer<typeof NetworkConfigSchema>;
|
|
118
|
+
export type WalletConfig = z.infer<typeof WalletConfigSchema>;
|
|
119
|
+
export type MobilestacksConfig = z.infer<typeof MobilestacksConfigSchema>;
|
|
120
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAIH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;EAQ9B,CAAC;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAInC,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const NetworkConfigSchema = z.object({
|
|
3
|
+
url: z.string().url(),
|
|
4
|
+
name: z.string(),
|
|
5
|
+
explorerUrl: z.string().url().optional(),
|
|
6
|
+
faucetUrl: z.string().url().nullable().optional(),
|
|
7
|
+
});
|
|
8
|
+
// Wallet config: allow privateKey, seedPhrase, or both. If both, privateKey is used.
|
|
9
|
+
export const WalletConfigSchema = z.object({
|
|
10
|
+
privateKey: z.string().min(1, "Private key is required").optional(),
|
|
11
|
+
seedPhrase: z.string().optional(),
|
|
12
|
+
derivationPath: z.string().optional(),
|
|
13
|
+
address: z.string().optional(),
|
|
14
|
+
}).refine((data) => data.privateKey || data.seedPhrase, { message: "Either privateKey or seedPhrase is required" });
|
|
15
|
+
export const MobilestacksConfigSchema = z.object({
|
|
16
|
+
networks: z.record(NetworkConfigSchema),
|
|
17
|
+
defaultNetwork: z.string(),
|
|
18
|
+
wallet: WalletConfigSchema,
|
|
19
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mobilestacks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Professional Task Runner & CLI for the Stacks Blockchain",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mobilestacks": "dist/cli/index.js"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"src/",
|
|
14
|
+
"contracts/",
|
|
15
|
+
"Clarinet.toml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"start": "node ./dist/cli/index.js",
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"lint": "eslint src mobilestacks.config.ts --ext .ts",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"setup:simnet": "node scripts/setup-simnet.mjs",
|
|
25
|
+
"prepublishOnly": "npm run build && npm test"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"stacks",
|
|
29
|
+
"blockchain",
|
|
30
|
+
"clarity",
|
|
31
|
+
"smart-contracts",
|
|
32
|
+
"cli",
|
|
33
|
+
"task-runner",
|
|
34
|
+
"web3",
|
|
35
|
+
"bitcoin"
|
|
36
|
+
],
|
|
37
|
+
"author": "mobilestacks",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/mobilestacks/mobilestacks.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/mobilestacks/mobilestacks#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/mobilestacks/mobilestacks/issues"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@hirosystems/clarinet-sdk": "^3.8.1",
|
|
52
|
+
"@types/inquirer": "^8.2.6",
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
|
54
|
+
"@typescript-eslint/parser": "^6.20.0",
|
|
55
|
+
"eslint": "^8.56.0",
|
|
56
|
+
"eslint-config-prettier": "^10.1.8",
|
|
57
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
58
|
+
"eslint-plugin-import": "^2.32.0",
|
|
59
|
+
"vitest": "^4.0.18"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@stacks/network": "latest",
|
|
63
|
+
"@stacks/transactions": "latest",
|
|
64
|
+
"@stacks/wallet-sdk": "latest",
|
|
65
|
+
"commander": "^11.0.0",
|
|
66
|
+
"node-fetch": "^3.3.2",
|
|
67
|
+
"ts-node": "^10.9.1",
|
|
68
|
+
"zod": "^3.25.76"
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { loadConfig } from '../config/config-loading';
|
|
5
|
+
import { RuntimeEnvironment } from '../core/runtime-environment';
|
|
6
|
+
import { TaskDefinitions } from '../core/tasks-definitions';
|
|
7
|
+
import { runInit } from './init';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
|
|
10
|
+
// Import all user tasks
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
// Auto-load all tasks in src/tasks
|
|
14
|
+
const tasksDir = path.join(__dirname, '../tasks');
|
|
15
|
+
fs.readdirSync(tasksDir)
|
|
16
|
+
.filter(f => f.endsWith('.ts') || f.endsWith('.js'))
|
|
17
|
+
.forEach(f => {
|
|
18
|
+
require(path.join(tasksDir, f));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const program = new Command();
|
|
22
|
+
program
|
|
23
|
+
.name('mobilestacks')
|
|
24
|
+
.description('Professional Task Runner for Stacks')
|
|
25
|
+
.version('0.1.0');
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// Init command for project scaffolding
|
|
29
|
+
program
|
|
30
|
+
.command('init')
|
|
31
|
+
.description('Scaffold a new mobilestacks project and config')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
await runInit();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// List all tasks if no command is given
|
|
38
|
+
program.action(() => {
|
|
39
|
+
console.log(chalk.bold.blue('\nMobilestacks - Professional Task Runner for Stacks\n'));
|
|
40
|
+
console.log(chalk.white('USAGE: ') + chalk.green('mobilestacks <task> [options]\n'));
|
|
41
|
+
console.log(chalk.bold('Available tasks:'));
|
|
42
|
+
TaskDefinitions.getInstance().getAllTasks().forEach(task => {
|
|
43
|
+
const params = task.params.map(p => chalk.yellow(`--${p.name}`)).join(' ');
|
|
44
|
+
console.log(' ' + chalk.cyan(task.name) + ' ' + params);
|
|
45
|
+
console.log(' ' + chalk.gray(task.description));
|
|
46
|
+
});
|
|
47
|
+
console.log('\n' + chalk.white('Use ') + chalk.green('mobilestacks <task> --help') + chalk.white(' for more info on a task.'));
|
|
48
|
+
console.log(chalk.white('\nExample:'));
|
|
49
|
+
console.log(' ' + chalk.green('mobilestacks deploy-contract --contractName my-contract --file ./contracts/my-contract.clar --network testnet'));
|
|
50
|
+
console.log(chalk.white('\nDocs: ') + chalk.underline('https://github.com/your-org/mobilestacks#readme'));
|
|
51
|
+
program.help({ error: false });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Dynamically add all registered tasks
|
|
55
|
+
|
|
56
|
+
TaskDefinitions.getInstance().getAllTasks().forEach(task => {
|
|
57
|
+
const cmd = program.command(task.name)
|
|
58
|
+
.description(task.description);
|
|
59
|
+
task.params.forEach(param => {
|
|
60
|
+
const optStr = `--${param.name} <value>`;
|
|
61
|
+
if (param.required !== false) {
|
|
62
|
+
cmd.option(optStr, param.description);
|
|
63
|
+
} else {
|
|
64
|
+
cmd.option(optStr, param.description, param.defaultValue as string | boolean | undefined);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
cmd.action(async (opts) => {
|
|
68
|
+
try {
|
|
69
|
+
// Prompt for missing required params
|
|
70
|
+
const missing = task.params.filter(p => p.required !== false && !opts[p.name]);
|
|
71
|
+
if (missing.length > 0) {
|
|
72
|
+
const answers = await inquirer.prompt(missing.map(p => ({
|
|
73
|
+
type: p.type === 'boolean' ? 'confirm' : 'input',
|
|
74
|
+
name: p.name,
|
|
75
|
+
message: p.description,
|
|
76
|
+
default: p.defaultValue
|
|
77
|
+
})));
|
|
78
|
+
Object.assign(opts, answers);
|
|
79
|
+
}
|
|
80
|
+
// Type conversion
|
|
81
|
+
task.params.forEach(p => {
|
|
82
|
+
if (opts[p.name] && p.type === 'number') opts[p.name] = Number(opts[p.name]);
|
|
83
|
+
if (opts[p.name] && p.type === 'boolean') opts[p.name] = Boolean(opts[p.name]);
|
|
84
|
+
});
|
|
85
|
+
const config = loadConfig();
|
|
86
|
+
const env = new RuntimeEnvironment(config);
|
|
87
|
+
const result = await task.action(opts, env);
|
|
88
|
+
if (typeof result === 'object') {
|
|
89
|
+
console.log(chalk.greenBright('Success!'));
|
|
90
|
+
console.dir(result, { depth: null, colors: true });
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.greenBright(result));
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
const error = err as Error;
|
|
96
|
+
console.error(chalk.redBright('Task failed:'), error.message || error);
|
|
97
|
+
if (error && typeof error === 'object' && 'stack' in error && error.stack) {
|
|
98
|
+
console.error(chalk.gray(error.stack));
|
|
99
|
+
}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
program.parse(process.argv);
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
|
|
5
|
+
export async function runInit() {
|
|
6
|
+
console.log('Welcome to mobilestacks project initialization!');
|
|
7
|
+
const answers = await inquirer.prompt([
|
|
8
|
+
{
|
|
9
|
+
type: 'input',
|
|
10
|
+
name: 'projectName',
|
|
11
|
+
message: 'Project name:',
|
|
12
|
+
default: path.basename(process.cwd()),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: 'input',
|
|
16
|
+
name: 'mainnetUrl',
|
|
17
|
+
message: 'Stacks mainnet node URL:',
|
|
18
|
+
default: 'https://stacks-node-api.mainnet.stacks.co',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
type: 'input',
|
|
22
|
+
name: 'testnetUrl',
|
|
23
|
+
message: 'Stacks testnet node URL:',
|
|
24
|
+
default: 'https://stacks-node-api.testnet.stacks.co',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'input',
|
|
28
|
+
name: 'privateKey',
|
|
29
|
+
message: 'Your wallet private key (leave blank to use seed phrase):',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'seedPhrase',
|
|
34
|
+
message: 'Your wallet seed phrase (leave blank if using private key):',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'input',
|
|
38
|
+
name: 'derivationPath',
|
|
39
|
+
message: 'Derivation path (default: m/44\'/5757\'/0\'/0/0):',
|
|
40
|
+
default: "m/44'/5757'/0'/0/0",
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const config = `export default {\n networks: {\n mainnet: { url: '${answers.mainnetUrl}', name: 'mainnet' },\n testnet: { url: '${answers.testnetUrl}', name: 'testnet' }\n },\n defaultNetwork: 'testnet',\n wallet: {\n ${answers.privateKey ? `privateKey: '${answers.privateKey}',` : ''}\n ${answers.seedPhrase ? `seedPhrase: '${answers.seedPhrase}',` : ''}\n derivationPath: '${answers.derivationPath}'\n }\n};\n`;
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(path.join(process.cwd(), 'mobilestacks.config.ts'), config);
|
|
47
|
+
// Scaffold example contract
|
|
48
|
+
const contractsDir = path.join(process.cwd(), 'contracts');
|
|
49
|
+
if (!fs.existsSync(contractsDir)) fs.mkdirSync(contractsDir);
|
|
50
|
+
fs.writeFileSync(path.join(contractsDir, 'sample-contract.clar'), '(define-public (hello-world)\n (ok "Hello, Stacks!"))\n');
|
|
51
|
+
// Scaffold example user task
|
|
52
|
+
const tasksDir = path.join(process.cwd(), 'src', 'tasks');
|
|
53
|
+
if (!fs.existsSync(tasksDir)) fs.mkdirSync(tasksDir, { recursive: true });
|
|
54
|
+
fs.writeFileSync(path.join(tasksDir, 'example-task.ts'),
|
|
55
|
+
"import { task } from '../core/dsl';\n\ntask('example', 'An example user task for onboarding')\n .addParam('name', 'Your name', { type: 'string', required: false, defaultValue: 'World' })\n .setAction(async (args) => {\n return `Hello, ${args.name}! Welcome to mobilestacks.`;\n });\n");
|
|
56
|
+
console.log('Created mobilestacks.config.ts, contracts/sample-contract.clar, and src/tasks/example-task.ts!');
|
|
57
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MobilestacksConfigSchema, MobilestacksConfig } from '../types/config';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { env } from '../core/env';
|
|
5
|
+
|
|
6
|
+
export function loadConfig(configPath?: string, cliOverrides: Record<string, unknown> = {}): MobilestacksConfig {
|
|
7
|
+
const configFile = configPath || path.resolve(process.cwd(), 'mobilestacks.config.ts');
|
|
8
|
+
if (!fs.existsSync(configFile)) {
|
|
9
|
+
throw new Error(`Config file not found: ${configFile}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
13
|
+
require('ts-node').register();
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
15
|
+
const userConfig = require(configFile);
|
|
16
|
+
let config = userConfig.default || userConfig;
|
|
17
|
+
|
|
18
|
+
// .env override
|
|
19
|
+
if (env.privateKey) config.wallet.privateKey = env.privateKey;
|
|
20
|
+
if (env.mainnetUrl) config.networks.mainnet.url = env.mainnetUrl;
|
|
21
|
+
if (env.testnetUrl) config.networks.testnet.url = env.testnetUrl;
|
|
22
|
+
|
|
23
|
+
// CLI overrides
|
|
24
|
+
config = { ...config, ...cliOverrides };
|
|
25
|
+
|
|
26
|
+
const parsed = MobilestacksConfigSchema.safeParse(config);
|
|
27
|
+
if (!parsed.success) {
|
|
28
|
+
throw new Error('Invalid mobilestacks.config.ts: ' + JSON.stringify(parsed.error.format(), null, 2));
|
|
29
|
+
}
|
|
30
|
+
return parsed.data;
|
|
31
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { task } from './dsl';
|
|
3
|
+
import { TaskDefinitions } from './tasks-definitions';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { RuntimeEnvironment } from './runtime-environment';
|
|
6
|
+
import { extendEnvironment, Extender } from './extender';
|
|
7
|
+
|
|
8
|
+
describe('DSL', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Clear tasks before each test
|
|
11
|
+
(TaskDefinitions.getInstance() as unknown as { _tasks: unknown[] })._tasks = [];
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should register a task with generic parameters', () => {
|
|
15
|
+
task('test-task', 'A test task')
|
|
16
|
+
.addParam('p1', 'param 1')
|
|
17
|
+
.setAction(async () => { return 'done'; });
|
|
18
|
+
|
|
19
|
+
const def = TaskDefinitions.getInstance().getTask('test-task');
|
|
20
|
+
expect(def).toBeDefined();
|
|
21
|
+
expect(def?.name).toBe('test-task');
|
|
22
|
+
expect(def?.params.length).toBe(1);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should validate Zod schema', async () => {
|
|
26
|
+
task('zod-task', 'Task with validation')
|
|
27
|
+
.addParam('age', 'Age param', { schema: z.number().min(18) })
|
|
28
|
+
.setAction(async (args) => {
|
|
29
|
+
return args.age;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const def = TaskDefinitions.getInstance().getTask('zod-task');
|
|
33
|
+
expect(def).toBeDefined();
|
|
34
|
+
|
|
35
|
+
// Mock environment
|
|
36
|
+
const env = {} as RuntimeEnvironment;
|
|
37
|
+
|
|
38
|
+
// Valid call
|
|
39
|
+
const result = await def?.action({ age: 20 }, env);
|
|
40
|
+
expect(result).toBe(20);
|
|
41
|
+
|
|
42
|
+
// Invalid call
|
|
43
|
+
await expect(def?.action({ age: 10 }, env)).rejects.toThrow('Invalid value for parameter \'age\'');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should support environment extensions', () => {
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
// Clear extensions
|
|
50
|
+
(Extender.getInstance() as unknown as { _extensions: unknown[] })._extensions = [];
|
|
51
|
+
|
|
52
|
+
extendEnvironment((env: RuntimeEnvironment) => {
|
|
53
|
+
env['foo'] = 'bar';
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const extensions = Extender.getInstance().getExtensions();
|
|
57
|
+
expect(extensions.length).toBe(1);
|
|
58
|
+
|
|
59
|
+
const mockEnv = {} as RuntimeEnvironment;
|
|
60
|
+
extensions[0](mockEnv);
|
|
61
|
+
expect(mockEnv['foo']).toBe('bar');
|
|
62
|
+
});
|
|
63
|
+
});
|
package/src/core/dsl.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { TaskDefinitions, TaskDefinition, TaskParam, TaskParamType } from './tasks-definitions';
|
|
2
|
+
import { ZodSchema } from 'zod';
|
|
3
|
+
import { RuntimeEnvironment } from './runtime-environment';
|
|
4
|
+
import { extendEnvironment } from './extender';
|
|
5
|
+
|
|
6
|
+
function task(name: string, description: string) {
|
|
7
|
+
const params: TaskParam[] = [];
|
|
8
|
+
return {
|
|
9
|
+
addParam(
|
|
10
|
+
paramName: string,
|
|
11
|
+
paramDesc: string,
|
|
12
|
+
options?: { type?: TaskParamType; required?: boolean; defaultValue?: unknown; schema?: ZodSchema }
|
|
13
|
+
) {
|
|
14
|
+
params.push({
|
|
15
|
+
name: paramName,
|
|
16
|
+
description: paramDesc,
|
|
17
|
+
type: options?.type || 'string',
|
|
18
|
+
required: options?.required !== false, // default true
|
|
19
|
+
defaultValue: options?.defaultValue,
|
|
20
|
+
schema: options?.schema,
|
|
21
|
+
});
|
|
22
|
+
return this;
|
|
23
|
+
},
|
|
24
|
+
setAction(action: (args: Record<string, unknown>, env: RuntimeEnvironment) => Promise<unknown>) {
|
|
25
|
+
const def: TaskDefinition = {
|
|
26
|
+
name,
|
|
27
|
+
description,
|
|
28
|
+
params,
|
|
29
|
+
action: async (args: Record<string, unknown>, env: RuntimeEnvironment) => {
|
|
30
|
+
// Validate args against schemas if present
|
|
31
|
+
for (const param of params) {
|
|
32
|
+
if (param.schema && args[param.name] !== undefined) {
|
|
33
|
+
try {
|
|
34
|
+
param.schema.parse(args[param.name]);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(`Invalid value for parameter '${param.name}': ${error}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return action(args, env);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
TaskDefinitions.getInstance().addTask(def);
|
|
44
|
+
return def;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function subtask(name: string, description: string, parentTaskName?: string) {
|
|
50
|
+
// Register as a subtask with a parent if provided
|
|
51
|
+
const sub = task(name, description);
|
|
52
|
+
if (parentTaskName) {
|
|
53
|
+
// Attach parent info for future use (e.g., dependency graph, CLI grouping)
|
|
54
|
+
const def = TaskDefinitions.getInstance().getTask(name);
|
|
55
|
+
if (def) (def as TaskDefinition & { parent?: string }).parent = parentTaskName;
|
|
56
|
+
}
|
|
57
|
+
return sub;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Workflow system: define and run ordered task/subtask sequences
|
|
61
|
+
export type WorkflowStep = { taskName: string; args?: Record<string, unknown> };
|
|
62
|
+
export type Workflow = WorkflowStep[];
|
|
63
|
+
|
|
64
|
+
export async function runWorkflow(workflow: Workflow, env: RuntimeEnvironment) {
|
|
65
|
+
for (const step of workflow) {
|
|
66
|
+
const def = TaskDefinitions.getInstance().getTask(step.taskName);
|
|
67
|
+
if (!def) throw new Error(`Task not found: ${step.taskName}`);
|
|
68
|
+
// eslint-disable-next-line no-await-in-loop
|
|
69
|
+
await def.action(step.args || {}, env);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { task, subtask, extendEnvironment };
|
package/src/core/env.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { RuntimeEnvironment } from './runtime-environment';
|
|
2
|
+
|
|
3
|
+
export type ExtensionFunc = (env: RuntimeEnvironment) => void;
|
|
4
|
+
|
|
5
|
+
export class Extender {
|
|
6
|
+
private static _instance: Extender;
|
|
7
|
+
private _extensions: ExtensionFunc[] = [];
|
|
8
|
+
|
|
9
|
+
private constructor() {}
|
|
10
|
+
|
|
11
|
+
public static getInstance(): Extender {
|
|
12
|
+
if (!Extender._instance) {
|
|
13
|
+
Extender._instance = new Extender();
|
|
14
|
+
}
|
|
15
|
+
return Extender._instance;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public addExtension(func: ExtensionFunc) {
|
|
19
|
+
this._extensions.push(func);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public getExtensions(): ExtensionFunc[] {
|
|
23
|
+
return this._extensions;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function extendEnvironment(func: ExtensionFunc) {
|
|
28
|
+
Extender.getInstance().addExtension(func);
|
|
29
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MobilestacksConfig, NetworkConfig } from '../types/config';
|
|
2
|
+
import { getAddressFromPrivateKey } from '@stacks/transactions';
|
|
3
|
+
import { generateWallet } from '@stacks/wallet-sdk';
|
|
4
|
+
import { StacksNetwork, createNetwork } from '@stacks/network';
|
|
5
|
+
import { Extender } from './extender';
|
|
6
|
+
|
|
7
|
+
export class RuntimeEnvironment {
|
|
8
|
+
public config: MobilestacksConfig;
|
|
9
|
+
public network: StacksNetwork;
|
|
10
|
+
public wallet!: { privateKey: string; address: string; accountIndex?: number };
|
|
11
|
+
public stacks: Record<string, unknown>; // Extension point
|
|
12
|
+
[key: string]: unknown; // Allow extensions
|
|
13
|
+
|
|
14
|
+
constructor(config: MobilestacksConfig) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
const networkName = config.defaultNetwork;
|
|
17
|
+
const networkConfig: NetworkConfig = config.networks[networkName];
|
|
18
|
+
if (!networkConfig) {
|
|
19
|
+
throw new Error(`Network config not found for: ${networkName}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Instantiate proper StacksNetwork
|
|
23
|
+
if (networkName === 'mainnet' || networkName === 'testnet') {
|
|
24
|
+
this.network = createNetwork({
|
|
25
|
+
network: networkName,
|
|
26
|
+
client: { baseUrl: networkConfig.url }
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
// Default to testnet structure for custom networks
|
|
30
|
+
this.network = createNetwork({
|
|
31
|
+
network: 'testnet',
|
|
32
|
+
client: { baseUrl: networkConfig.url }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Wallet: support privateKey, seedPhrase+derivationPath, or both (prefer privateKey)
|
|
37
|
+
if (config.wallet.privateKey) {
|
|
38
|
+
// Derive address from privateKey
|
|
39
|
+
const address = getAddressFromPrivateKey(config.wallet.privateKey, networkName === 'mainnet' ? 'mainnet' : 'testnet');
|
|
40
|
+
this.wallet = {
|
|
41
|
+
privateKey: config.wallet.privateKey,
|
|
42
|
+
address,
|
|
43
|
+
};
|
|
44
|
+
} else if (config.wallet.seedPhrase) {
|
|
45
|
+
const path = config.wallet.derivationPath || "m/44'/5757'/0'/0/0"; // Stacks main path
|
|
46
|
+
// generateWallet requires a password param
|
|
47
|
+
generateWallet({ secretKey: config.wallet.seedPhrase, password: '' }).then(wallet => {
|
|
48
|
+
const index = parseInt(path.split('/').pop() || '0', 10);
|
|
49
|
+
const account = wallet.accounts[index] || wallet.accounts[0];
|
|
50
|
+
// Use getStxAddress to get the address
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
52
|
+
const { getStxAddress } = require('@stacks/wallet-sdk/dist/models/account');
|
|
53
|
+
// network.addressVersion has singleSig/multiSig lines.
|
|
54
|
+
const address = getStxAddress(account, this.network.transactionVersion);
|
|
55
|
+
|
|
56
|
+
this.wallet = {
|
|
57
|
+
privateKey: account.stxPrivateKey,
|
|
58
|
+
address,
|
|
59
|
+
accountIndex: account.index,
|
|
60
|
+
};
|
|
61
|
+
}).catch(() => {
|
|
62
|
+
throw new Error('Failed to generate wallet from seed phrase');
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error('No privateKey or seedPhrase provided in wallet config');
|
|
66
|
+
}
|
|
67
|
+
this.stacks = {};
|
|
68
|
+
|
|
69
|
+
// Apply extensions
|
|
70
|
+
for (const extension of Extender.getInstance().getExtensions()) {
|
|
71
|
+
extension(this);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|