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
package/Clarinet.toml
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mobilestacks contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# mobilestacks
|
|
2
|
+
|
|
3
|
+
A Hardhat-style development framework for Stacks. Write, test, and deploy Clarity smart contracts with a task-based CLI, local Simnet testing, and a pluggable runtime.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mobilestacks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
Scaffold a new project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx mobilestacks init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This generates a config file, a sample Clarity contract, and an example task.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
All config lives in `mobilestacks.config.ts`:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
export default {
|
|
27
|
+
networks: {
|
|
28
|
+
mainnet: { url: "https://stacks-node-api.mainnet.stacks.co", name: "mainnet" },
|
|
29
|
+
testnet: { url: "https://stacks-node-api.testnet.stacks.co", name: "testnet" },
|
|
30
|
+
},
|
|
31
|
+
defaultNetwork: "testnet",
|
|
32
|
+
wallet: {
|
|
33
|
+
privateKey: process.env.STACKS_PRIVATE_KEY,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Secrets can live in `.env` — they override config values automatically.
|
|
39
|
+
|
|
40
|
+
## CLI
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx mobilestacks # list all tasks
|
|
44
|
+
npx mobilestacks deploy-contract # deploy a contract
|
|
45
|
+
npx mobilestacks get-balance # check STX balance
|
|
46
|
+
npx mobilestacks send-stx # send STX
|
|
47
|
+
npx mobilestacks faucet-request # get testnet tokens
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Missing params are prompted interactively. Run any task with `--help` for options.
|
|
51
|
+
|
|
52
|
+
### Built-in Tasks
|
|
53
|
+
|
|
54
|
+
| Task | What it does |
|
|
55
|
+
| ---- | ------------ |
|
|
56
|
+
| `deploy-contract` | Deploy a `.clar` file to mainnet/testnet |
|
|
57
|
+
| `send-stx` | Transfer STX to an address |
|
|
58
|
+
| `get-balance` | Check STX balance for any address |
|
|
59
|
+
| `faucet-request` | Request testnet STX from the faucet |
|
|
60
|
+
| `list-accounts` | List derived wallet accounts |
|
|
61
|
+
| `get-tx-history` | Fetch recent transactions |
|
|
62
|
+
| `call-contract-function` | Call a read-only contract function |
|
|
63
|
+
| `get-contract-info` | Fetch deployed contract metadata |
|
|
64
|
+
| `verify-contract` | Diff on-chain source against a local file |
|
|
65
|
+
|
|
66
|
+
## Writing Tasks
|
|
67
|
+
|
|
68
|
+
Drop a file in `src/tasks/` — it's auto-discovered:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { task } from "mobilestacks";
|
|
72
|
+
import { z } from "zod";
|
|
73
|
+
|
|
74
|
+
task("greet", "Say hello")
|
|
75
|
+
.addParam("name", "Who to greet", { schema: z.string().min(1) })
|
|
76
|
+
.setAction(async (args, env) => {
|
|
77
|
+
return `Hello, ${args.name}! (network: ${env.config.defaultNetwork})`;
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Subtasks and workflows are also supported:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { subtask, runWorkflow } from "mobilestacks";
|
|
85
|
+
|
|
86
|
+
// subtask tied to a parent
|
|
87
|
+
subtask("deploy:validate", "Pre-deploy check", "deploy-contract")
|
|
88
|
+
.setAction(async (args, env) => { /* ... */ });
|
|
89
|
+
|
|
90
|
+
// run tasks in sequence
|
|
91
|
+
await runWorkflow([
|
|
92
|
+
{ taskName: "deploy-contract", args: { /* ... */ } },
|
|
93
|
+
{ taskName: "verify-contract", args: { /* ... */ } },
|
|
94
|
+
], env);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Extending the Runtime
|
|
98
|
+
|
|
99
|
+
Add custom properties to the runtime environment, available in every task:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { extendEnvironment } from "mobilestacks";
|
|
103
|
+
|
|
104
|
+
extendEnvironment((env) => {
|
|
105
|
+
env.formatSTX = (micro: number) => `${(micro / 1e6).toFixed(6)} STX`;
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Testing with Simnet
|
|
110
|
+
|
|
111
|
+
Test contracts locally using the Clarinet SDK — no devnet needed:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
115
|
+
import { Simnet } from "mobilestacks";
|
|
116
|
+
import { Cl } from "@stacks/transactions";
|
|
117
|
+
|
|
118
|
+
describe("My Contract", () => {
|
|
119
|
+
let simnet: Simnet;
|
|
120
|
+
|
|
121
|
+
beforeAll(async () => {
|
|
122
|
+
simnet = await Simnet.init();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("calls hello-world", () => {
|
|
126
|
+
const deployer = simnet.getDeployer();
|
|
127
|
+
const { result } = simnet.callPublic("sample-contract", "hello-world", [], deployer);
|
|
128
|
+
expect(result).toStrictEqual(Cl.ok(Cl.stringAscii("Hello, Stacks!")));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Setup
|
|
134
|
+
|
|
135
|
+
Contract tests need a `Clarinet.toml` and test accounts. Run the setup script once:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
npm run setup:simnet
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then run tests:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm test
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Programmatic API
|
|
148
|
+
|
|
149
|
+
Use mobilestacks as a library:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { task, subtask, extendEnvironment, Simnet, RuntimeEnvironment } from "mobilestacks";
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Project Structure
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
├── src/
|
|
159
|
+
│ ├── cli/ # CLI entry point + init scaffolding
|
|
160
|
+
│ ├── core/ # DSL, Simnet, RuntimeEnvironment, task registry
|
|
161
|
+
│ ├── tasks/ # Built-in tasks (auto-loaded)
|
|
162
|
+
│ ├── types/ # Zod schemas + TypeScript types
|
|
163
|
+
│ └── index.ts # Public API
|
|
164
|
+
├── tests/ # Vitest test files
|
|
165
|
+
├── contracts/ # Clarity contracts
|
|
166
|
+
├── Clarinet.toml # Clarinet project manifest
|
|
167
|
+
└── mobilestacks.config.ts
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Contributing
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
git clone https://github.com/Wizbisy/mobilestacks.git
|
|
174
|
+
cd mobilestacks
|
|
175
|
+
npm install
|
|
176
|
+
npm run setup:simnet # generate test accounts
|
|
177
|
+
npm test # run all tests
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
181
|
+
|
|
182
|
+
## Security
|
|
183
|
+
|
|
184
|
+
Private keys and seed phrases are never logged. Output is scanned for secrets and masked automatically. Keep your `.env` out of version control.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
networks: {
|
|
3
|
+
mainnet: {
|
|
4
|
+
url: string;
|
|
5
|
+
name: string;
|
|
6
|
+
explorerUrl: string;
|
|
7
|
+
faucetUrl: null;
|
|
8
|
+
};
|
|
9
|
+
testnet: {
|
|
10
|
+
url: string;
|
|
11
|
+
name: string;
|
|
12
|
+
explorerUrl: string;
|
|
13
|
+
faucetUrl: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
defaultNetwork: string;
|
|
17
|
+
wallet: {};
|
|
18
|
+
};
|
|
19
|
+
export default _default;
|
|
20
|
+
//# sourceMappingURL=mobilestacks.config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobilestacks.config.d.ts","sourceRoot":"","sources":["../mobilestacks.config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AACA,wBAqBE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Example mobilestacks.config.ts for testing
|
|
2
|
+
export default {
|
|
3
|
+
networks: {
|
|
4
|
+
mainnet: {
|
|
5
|
+
url: "https://stacks-node-api.mainnet.stacks.co",
|
|
6
|
+
name: "mainnet",
|
|
7
|
+
explorerUrl: "https://explorer.stacks.co",
|
|
8
|
+
faucetUrl: null
|
|
9
|
+
},
|
|
10
|
+
testnet: {
|
|
11
|
+
url: "https://stacks-node-api.testnet.stacks.co",
|
|
12
|
+
name: "testnet",
|
|
13
|
+
explorerUrl: "https://explorer.stacks.co?chain=testnet",
|
|
14
|
+
faucetUrl: "https://stacks-node-api.testnet.stacks.co/extended/v1/faucet/stx"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
defaultNetwork: "testnet",
|
|
18
|
+
wallet: {
|
|
19
|
+
// privateKey: "YOUR_PRIVATE_KEY_HERE",
|
|
20
|
+
// seedPhrase: "your twelve word phrase here",
|
|
21
|
+
// derivationPath: "m/44'/5757'/0'/0/0"
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
// Import all user tasks
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
// Auto-load all tasks in src/tasks
|
|
13
|
+
const tasksDir = path.join(__dirname, '../tasks');
|
|
14
|
+
fs.readdirSync(tasksDir)
|
|
15
|
+
.filter(f => f.endsWith('.ts') || f.endsWith('.js'))
|
|
16
|
+
.forEach(f => {
|
|
17
|
+
require(path.join(tasksDir, f));
|
|
18
|
+
});
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name('mobilestacks')
|
|
22
|
+
.description('Professional Task Runner for Stacks')
|
|
23
|
+
.version('0.1.0');
|
|
24
|
+
// Init command for project scaffolding
|
|
25
|
+
program
|
|
26
|
+
.command('init')
|
|
27
|
+
.description('Scaffold a new mobilestacks project and config')
|
|
28
|
+
.action(async () => {
|
|
29
|
+
await runInit();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
32
|
+
// List all tasks if no command is given
|
|
33
|
+
program.action(() => {
|
|
34
|
+
console.log(chalk.bold.blue('\nMobilestacks - Professional Task Runner for Stacks\n'));
|
|
35
|
+
console.log(chalk.white('USAGE: ') + chalk.green('mobilestacks <task> [options]\n'));
|
|
36
|
+
console.log(chalk.bold('Available tasks:'));
|
|
37
|
+
TaskDefinitions.getInstance().getAllTasks().forEach(task => {
|
|
38
|
+
const params = task.params.map(p => chalk.yellow(`--${p.name}`)).join(' ');
|
|
39
|
+
console.log(' ' + chalk.cyan(task.name) + ' ' + params);
|
|
40
|
+
console.log(' ' + chalk.gray(task.description));
|
|
41
|
+
});
|
|
42
|
+
console.log('\n' + chalk.white('Use ') + chalk.green('mobilestacks <task> --help') + chalk.white(' for more info on a task.'));
|
|
43
|
+
console.log(chalk.white('\nExample:'));
|
|
44
|
+
console.log(' ' + chalk.green('mobilestacks deploy-contract --contractName my-contract --file ./contracts/my-contract.clar --network testnet'));
|
|
45
|
+
console.log(chalk.white('\nDocs: ') + chalk.underline('https://github.com/your-org/mobilestacks#readme'));
|
|
46
|
+
program.help({ error: false });
|
|
47
|
+
});
|
|
48
|
+
// Dynamically add all registered tasks
|
|
49
|
+
TaskDefinitions.getInstance().getAllTasks().forEach(task => {
|
|
50
|
+
const cmd = program.command(task.name)
|
|
51
|
+
.description(task.description);
|
|
52
|
+
task.params.forEach(param => {
|
|
53
|
+
const optStr = `--${param.name} <value>`;
|
|
54
|
+
if (param.required !== false) {
|
|
55
|
+
cmd.option(optStr, param.description);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
cmd.option(optStr, param.description, param.defaultValue);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
cmd.action(async (opts) => {
|
|
62
|
+
try {
|
|
63
|
+
// Prompt for missing required params
|
|
64
|
+
const missing = task.params.filter(p => p.required !== false && !opts[p.name]);
|
|
65
|
+
if (missing.length > 0) {
|
|
66
|
+
const answers = await inquirer.prompt(missing.map(p => ({
|
|
67
|
+
type: p.type === 'boolean' ? 'confirm' : 'input',
|
|
68
|
+
name: p.name,
|
|
69
|
+
message: p.description,
|
|
70
|
+
default: p.defaultValue
|
|
71
|
+
})));
|
|
72
|
+
Object.assign(opts, answers);
|
|
73
|
+
}
|
|
74
|
+
// Type conversion
|
|
75
|
+
task.params.forEach(p => {
|
|
76
|
+
if (opts[p.name] && p.type === 'number')
|
|
77
|
+
opts[p.name] = Number(opts[p.name]);
|
|
78
|
+
if (opts[p.name] && p.type === 'boolean')
|
|
79
|
+
opts[p.name] = Boolean(opts[p.name]);
|
|
80
|
+
});
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
const env = new RuntimeEnvironment(config);
|
|
83
|
+
const result = await task.action(opts, env);
|
|
84
|
+
if (typeof result === 'object') {
|
|
85
|
+
console.log(chalk.greenBright('Success!'));
|
|
86
|
+
console.dir(result, { depth: null, colors: true });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log(chalk.greenBright(result));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const error = err;
|
|
94
|
+
console.error(chalk.redBright('Task failed:'), error.message || error);
|
|
95
|
+
if (error && typeof error === 'object' && 'stack' in error && error.stack) {
|
|
96
|
+
console.error(chalk.gray(error.stack));
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/init.ts"],"names":[],"mappings":"AAIA,wBAAsB,OAAO,kBAoD5B"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
export async function runInit() {
|
|
5
|
+
console.log('Welcome to mobilestacks project initialization!');
|
|
6
|
+
const answers = await inquirer.prompt([
|
|
7
|
+
{
|
|
8
|
+
type: 'input',
|
|
9
|
+
name: 'projectName',
|
|
10
|
+
message: 'Project name:',
|
|
11
|
+
default: path.basename(process.cwd()),
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'input',
|
|
15
|
+
name: 'mainnetUrl',
|
|
16
|
+
message: 'Stacks mainnet node URL:',
|
|
17
|
+
default: 'https://stacks-node-api.mainnet.stacks.co',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'input',
|
|
21
|
+
name: 'testnetUrl',
|
|
22
|
+
message: 'Stacks testnet node URL:',
|
|
23
|
+
default: 'https://stacks-node-api.testnet.stacks.co',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'input',
|
|
27
|
+
name: 'privateKey',
|
|
28
|
+
message: 'Your wallet private key (leave blank to use seed phrase):',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: 'input',
|
|
32
|
+
name: 'seedPhrase',
|
|
33
|
+
message: 'Your wallet seed phrase (leave blank if using private key):',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'derivationPath',
|
|
38
|
+
message: 'Derivation path (default: m/44\'/5757\'/0\'/0/0):',
|
|
39
|
+
default: "m/44'/5757'/0'/0/0",
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
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`;
|
|
43
|
+
fs.writeFileSync(path.join(process.cwd(), 'mobilestacks.config.ts'), config);
|
|
44
|
+
// Scaffold example contract
|
|
45
|
+
const contractsDir = path.join(process.cwd(), 'contracts');
|
|
46
|
+
if (!fs.existsSync(contractsDir))
|
|
47
|
+
fs.mkdirSync(contractsDir);
|
|
48
|
+
fs.writeFileSync(path.join(contractsDir, 'sample-contract.clar'), '(define-public (hello-world)\n (ok "Hello, Stacks!"))\n');
|
|
49
|
+
// Scaffold example user task
|
|
50
|
+
const tasksDir = path.join(process.cwd(), 'src', 'tasks');
|
|
51
|
+
if (!fs.existsSync(tasksDir))
|
|
52
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(tasksDir, 'example-task.ts'), "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");
|
|
54
|
+
console.log('Created mobilestacks.config.ts, contracts/sample-contract.clar, and src/tasks/example-task.ts!');
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loading.d.ts","sourceRoot":"","sources":["../../../src/config/config-loading.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAK/E,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,kBAAkB,CAyB9G"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MobilestacksConfigSchema } from '../types/config';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { env } from '../core/env';
|
|
5
|
+
export function loadConfig(configPath, cliOverrides = {}) {
|
|
6
|
+
const configFile = configPath || path.resolve(process.cwd(), 'mobilestacks.config.ts');
|
|
7
|
+
if (!fs.existsSync(configFile)) {
|
|
8
|
+
throw new Error(`Config file not found: ${configFile}`);
|
|
9
|
+
}
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
11
|
+
require('ts-node').register();
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
13
|
+
const userConfig = require(configFile);
|
|
14
|
+
let config = userConfig.default || userConfig;
|
|
15
|
+
// .env override
|
|
16
|
+
if (env.privateKey)
|
|
17
|
+
config.wallet.privateKey = env.privateKey;
|
|
18
|
+
if (env.mainnetUrl)
|
|
19
|
+
config.networks.mainnet.url = env.mainnetUrl;
|
|
20
|
+
if (env.testnetUrl)
|
|
21
|
+
config.networks.testnet.url = env.testnetUrl;
|
|
22
|
+
// CLI overrides
|
|
23
|
+
config = { ...config, ...cliOverrides };
|
|
24
|
+
const parsed = MobilestacksConfigSchema.safeParse(config);
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
throw new Error('Invalid mobilestacks.config.ts: ' + JSON.stringify(parsed.error.format(), null, 2));
|
|
27
|
+
}
|
|
28
|
+
return parsed.data;
|
|
29
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { TaskDefinition, TaskParamType } from './tasks-definitions';
|
|
2
|
+
import { ZodSchema } from 'zod';
|
|
3
|
+
import { RuntimeEnvironment } from './runtime-environment';
|
|
4
|
+
import { extendEnvironment } from './extender';
|
|
5
|
+
declare function task(name: string, description: string): {
|
|
6
|
+
addParam(paramName: string, paramDesc: string, options?: {
|
|
7
|
+
type?: TaskParamType;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
defaultValue?: unknown;
|
|
10
|
+
schema?: ZodSchema;
|
|
11
|
+
}): /*elided*/ any;
|
|
12
|
+
setAction(action: (args: Record<string, unknown>, env: RuntimeEnvironment) => Promise<unknown>): TaskDefinition;
|
|
13
|
+
};
|
|
14
|
+
declare function subtask(name: string, description: string, parentTaskName?: string): {
|
|
15
|
+
addParam(paramName: string, paramDesc: string, options?: {
|
|
16
|
+
type?: TaskParamType;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
defaultValue?: unknown;
|
|
19
|
+
schema?: ZodSchema;
|
|
20
|
+
}): /*elided*/ any;
|
|
21
|
+
setAction(action: (args: Record<string, unknown>, env: RuntimeEnvironment) => Promise<unknown>): TaskDefinition;
|
|
22
|
+
};
|
|
23
|
+
export type WorkflowStep = {
|
|
24
|
+
taskName: string;
|
|
25
|
+
args?: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
export type Workflow = WorkflowStep[];
|
|
28
|
+
export declare function runWorkflow(workflow: Workflow, env: RuntimeEnvironment): Promise<void>;
|
|
29
|
+
export { task, subtask, extendEnvironment };
|
|
30
|
+
//# sourceMappingURL=dsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsl.d.ts","sourceRoot":"","sources":["../../../src/core/dsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,cAAc,EAAa,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAChG,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,iBAAS,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;wBAI9B,MAAM,aACN,MAAM,YACP;QAAE,IAAI,CAAC,EAAE,aAAa,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE;sBAYlF,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC;EAuBjG;AAED,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM;wBAvC1D,MAAM,aACN,MAAM,YACP;QAAE,IAAI,CAAC,EAAE,aAAa,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,SAAS,CAAA;KAAE;sBAYlF,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC;EAkCjG;AAGD,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAChF,MAAM,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;AAEtC,wBAAsB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,kBAAkB,iBAO5E;AAED,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { TaskDefinitions } from './tasks-definitions';
|
|
2
|
+
import { extendEnvironment } from './extender';
|
|
3
|
+
function task(name, description) {
|
|
4
|
+
const params = [];
|
|
5
|
+
return {
|
|
6
|
+
addParam(paramName, paramDesc, options) {
|
|
7
|
+
params.push({
|
|
8
|
+
name: paramName,
|
|
9
|
+
description: paramDesc,
|
|
10
|
+
type: options?.type || 'string',
|
|
11
|
+
required: options?.required !== false, // default true
|
|
12
|
+
defaultValue: options?.defaultValue,
|
|
13
|
+
schema: options?.schema,
|
|
14
|
+
});
|
|
15
|
+
return this;
|
|
16
|
+
},
|
|
17
|
+
setAction(action) {
|
|
18
|
+
const def = {
|
|
19
|
+
name,
|
|
20
|
+
description,
|
|
21
|
+
params,
|
|
22
|
+
action: async (args, env) => {
|
|
23
|
+
// Validate args against schemas if present
|
|
24
|
+
for (const param of params) {
|
|
25
|
+
if (param.schema && args[param.name] !== undefined) {
|
|
26
|
+
try {
|
|
27
|
+
param.schema.parse(args[param.name]);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Invalid value for parameter '${param.name}': ${error}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return action(args, env);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
TaskDefinitions.getInstance().addTask(def);
|
|
38
|
+
return def;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function subtask(name, description, parentTaskName) {
|
|
43
|
+
// Register as a subtask with a parent if provided
|
|
44
|
+
const sub = task(name, description);
|
|
45
|
+
if (parentTaskName) {
|
|
46
|
+
// Attach parent info for future use (e.g., dependency graph, CLI grouping)
|
|
47
|
+
const def = TaskDefinitions.getInstance().getTask(name);
|
|
48
|
+
if (def)
|
|
49
|
+
def.parent = parentTaskName;
|
|
50
|
+
}
|
|
51
|
+
return sub;
|
|
52
|
+
}
|
|
53
|
+
export async function runWorkflow(workflow, env) {
|
|
54
|
+
for (const step of workflow) {
|
|
55
|
+
const def = TaskDefinitions.getInstance().getTask(step.taskName);
|
|
56
|
+
if (!def)
|
|
57
|
+
throw new Error(`Task not found: ${step.taskName}`);
|
|
58
|
+
// eslint-disable-next-line no-await-in-loop
|
|
59
|
+
await def.action(step.args || {}, env);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export { task, subtask, extendEnvironment };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsl.test.d.ts","sourceRoot":"","sources":["../../../src/core/dsl.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 { extendEnvironment, Extender } from './extender';
|
|
6
|
+
describe('DSL', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Clear tasks before each test
|
|
9
|
+
TaskDefinitions.getInstance()._tasks = [];
|
|
10
|
+
});
|
|
11
|
+
it('should register a task with generic parameters', () => {
|
|
12
|
+
task('test-task', 'A test task')
|
|
13
|
+
.addParam('p1', 'param 1')
|
|
14
|
+
.setAction(async () => { return 'done'; });
|
|
15
|
+
const def = TaskDefinitions.getInstance().getTask('test-task');
|
|
16
|
+
expect(def).toBeDefined();
|
|
17
|
+
expect(def?.name).toBe('test-task');
|
|
18
|
+
expect(def?.params.length).toBe(1);
|
|
19
|
+
});
|
|
20
|
+
it('should validate Zod schema', async () => {
|
|
21
|
+
task('zod-task', 'Task with validation')
|
|
22
|
+
.addParam('age', 'Age param', { schema: z.number().min(18) })
|
|
23
|
+
.setAction(async (args) => {
|
|
24
|
+
return args.age;
|
|
25
|
+
});
|
|
26
|
+
const def = TaskDefinitions.getInstance().getTask('zod-task');
|
|
27
|
+
expect(def).toBeDefined();
|
|
28
|
+
// Mock environment
|
|
29
|
+
const env = {};
|
|
30
|
+
// Valid call
|
|
31
|
+
const result = await def?.action({ age: 20 }, env);
|
|
32
|
+
expect(result).toBe(20);
|
|
33
|
+
// Invalid call
|
|
34
|
+
await expect(def?.action({ age: 10 }, env)).rejects.toThrow('Invalid value for parameter \'age\'');
|
|
35
|
+
});
|
|
36
|
+
it('should support environment extensions', () => {
|
|
37
|
+
// Clear extensions
|
|
38
|
+
Extender.getInstance()._extensions = [];
|
|
39
|
+
extendEnvironment((env) => {
|
|
40
|
+
env['foo'] = 'bar';
|
|
41
|
+
});
|
|
42
|
+
const extensions = Extender.getInstance().getExtensions();
|
|
43
|
+
expect(extensions.length).toBe(1);
|
|
44
|
+
const mockEnv = {};
|
|
45
|
+
extensions[0](mockEnv);
|
|
46
|
+
expect(mockEnv['foo']).toBe('bar');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/core/env.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,GAAG;;;;CAIf,CAAC"}
|