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.
Files changed (96) hide show
  1. package/Clarinet.toml +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +188 -0
  4. package/contracts/sample-contract.clar +2 -0
  5. package/dist/mobilestacks.config.d.ts +20 -0
  6. package/dist/mobilestacks.config.d.ts.map +1 -0
  7. package/dist/mobilestacks.config.js +23 -0
  8. package/dist/src/cli/index.d.ts +3 -0
  9. package/dist/src/cli/index.d.ts.map +1 -0
  10. package/dist/src/cli/index.js +102 -0
  11. package/dist/src/cli/init.d.ts +2 -0
  12. package/dist/src/cli/init.d.ts.map +1 -0
  13. package/dist/src/cli/init.js +55 -0
  14. package/dist/src/config/config-loading.d.ts +3 -0
  15. package/dist/src/config/config-loading.d.ts.map +1 -0
  16. package/dist/src/config/config-loading.js +29 -0
  17. package/dist/src/core/dsl.d.ts +30 -0
  18. package/dist/src/core/dsl.d.ts.map +1 -0
  19. package/dist/src/core/dsl.js +62 -0
  20. package/dist/src/core/dsl.test.d.ts +2 -0
  21. package/dist/src/core/dsl.test.d.ts.map +1 -0
  22. package/dist/src/core/dsl.test.js +48 -0
  23. package/dist/src/core/env.d.ts +6 -0
  24. package/dist/src/core/env.d.ts.map +1 -0
  25. package/dist/src/core/env.js +7 -0
  26. package/dist/src/core/extender.d.ts +12 -0
  27. package/dist/src/core/extender.d.ts.map +1 -0
  28. package/dist/src/core/extender.js +20 -0
  29. package/dist/src/core/runtime-environment.d.ts +15 -0
  30. package/dist/src/core/runtime-environment.d.ts.map +1 -0
  31. package/dist/src/core/runtime-environment.js +65 -0
  32. package/dist/src/core/simnet.d.ts +17 -0
  33. package/dist/src/core/simnet.d.ts.map +1 -0
  34. package/dist/src/core/simnet.js +42 -0
  35. package/dist/src/core/tasks-definitions.d.ts +28 -0
  36. package/dist/src/core/tasks-definitions.d.ts.map +1 -0
  37. package/dist/src/core/tasks-definitions.js +21 -0
  38. package/dist/src/index.d.ts +5 -0
  39. package/dist/src/index.d.ts.map +1 -0
  40. package/dist/src/index.js +4 -0
  41. package/dist/src/tasks/call-contract-function.d.ts +2 -0
  42. package/dist/src/tasks/call-contract-function.d.ts.map +1 -0
  43. package/dist/src/tasks/call-contract-function.js +31 -0
  44. package/dist/src/tasks/deploy-contract.d.ts +2 -0
  45. package/dist/src/tasks/deploy-contract.d.ts.map +1 -0
  46. package/dist/src/tasks/deploy-contract.js +44 -0
  47. package/dist/src/tasks/example-task.d.ts +2 -0
  48. package/dist/src/tasks/example-task.d.ts.map +1 -0
  49. package/dist/src/tasks/example-task.js +6 -0
  50. package/dist/src/tasks/faucet-request.d.ts +2 -0
  51. package/dist/src/tasks/faucet-request.d.ts.map +1 -0
  52. package/dist/src/tasks/faucet-request.js +21 -0
  53. package/dist/src/tasks/get-balance.d.ts +2 -0
  54. package/dist/src/tasks/get-balance.d.ts.map +1 -0
  55. package/dist/src/tasks/get-balance.js +33 -0
  56. package/dist/src/tasks/get-contract-info.d.ts +2 -0
  57. package/dist/src/tasks/get-contract-info.d.ts.map +1 -0
  58. package/dist/src/tasks/get-contract-info.js +18 -0
  59. package/dist/src/tasks/get-tx-history.d.ts +2 -0
  60. package/dist/src/tasks/get-tx-history.d.ts.map +1 -0
  61. package/dist/src/tasks/get-tx-history.js +38 -0
  62. package/dist/src/tasks/list-accounts.d.ts +2 -0
  63. package/dist/src/tasks/list-accounts.d.ts.map +1 -0
  64. package/dist/src/tasks/list-accounts.js +24 -0
  65. package/dist/src/tasks/send-stx.d.ts +2 -0
  66. package/dist/src/tasks/send-stx.d.ts.map +1 -0
  67. package/dist/src/tasks/send-stx.js +36 -0
  68. package/dist/src/tasks/verify-contract.d.ts +2 -0
  69. package/dist/src/tasks/verify-contract.d.ts.map +1 -0
  70. package/dist/src/tasks/verify-contract.js +33 -0
  71. package/dist/src/types/config.d.ts +120 -0
  72. package/dist/src/types/config.d.ts.map +1 -0
  73. package/dist/src/types/config.js +19 -0
  74. package/package.json +70 -0
  75. package/src/cli/index.ts +105 -0
  76. package/src/cli/init.ts +57 -0
  77. package/src/config/config-loading.ts +31 -0
  78. package/src/core/dsl.test.ts +63 -0
  79. package/src/core/dsl.ts +73 -0
  80. package/src/core/env.ts +8 -0
  81. package/src/core/extender.ts +29 -0
  82. package/src/core/runtime-environment.ts +74 -0
  83. package/src/core/simnet.ts +56 -0
  84. package/src/core/tasks-definitions.ts +47 -0
  85. package/src/index.ts +4 -0
  86. package/src/tasks/call-contract-function.ts +31 -0
  87. package/src/tasks/deploy-contract.ts +49 -0
  88. package/src/tasks/example-task.ts +7 -0
  89. package/src/tasks/faucet-request.ts +21 -0
  90. package/src/tasks/get-balance.ts +34 -0
  91. package/src/tasks/get-contract-info.ts +18 -0
  92. package/src/tasks/get-tx-history.ts +39 -0
  93. package/src/tasks/list-accounts.ts +27 -0
  94. package/src/tasks/send-stx.ts +39 -0
  95. package/src/tasks/verify-contract.ts +33 -0
  96. package/src/types/config.ts +30 -0
package/Clarinet.toml ADDED
@@ -0,0 +1,7 @@
1
+ [project]
2
+ name = "mobilestacks"
3
+ requirements = []
4
+
5
+ [contracts.sample-contract]
6
+ path = "contracts/sample-contract.clar"
7
+ clarity_version = 1
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,2 @@
1
+ (define-public (hello-world)
2
+ (ok "Hello, Stacks!"))
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export declare function runInit(): Promise<void>;
2
+ //# sourceMappingURL=init.d.ts.map
@@ -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,3 @@
1
+ import { MobilestacksConfig } from '../types/config';
2
+ export declare function loadConfig(configPath?: string, cliOverrides?: Record<string, unknown>): MobilestacksConfig;
3
+ //# sourceMappingURL=config-loading.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dsl.test.d.ts.map
@@ -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,6 @@
1
+ export declare const env: {
2
+ privateKey: string | undefined;
3
+ mainnetUrl: string | undefined;
4
+ testnetUrl: string | undefined;
5
+ };
6
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../../src/core/env.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,GAAG;;;;CAIf,CAAC"}
@@ -0,0 +1,7 @@
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+ export const env = {
4
+ privateKey: process.env.STACKS_PRIVATE_KEY,
5
+ mainnetUrl: process.env.STACKS_MAINNET_URL,
6
+ testnetUrl: process.env.STACKS_TESTNET_URL,
7
+ };