@harmoniclabs/pebble-cli 0.1.0-dev1

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.
@@ -0,0 +1,3 @@
1
+ import { CliCompileOptions } from "./completeCompileOptions.js";
2
+ export declare function compilePebbleProject(opts: CliCompileOptions): Promise<void>;
3
+ export default compilePebbleProject;
@@ -0,0 +1,76 @@
1
+ import * as path from "node:path";
2
+ import * as fsp from "node:fs/promises";
3
+ import { existsSync } from "node:fs";
4
+ import { Compiler } from "@harmoniclabs/pebble";
5
+ function resolvePath(filename, baseDir) {
6
+ if (!filename)
7
+ return baseDir;
8
+ if (path.isAbsolute(filename))
9
+ return filename;
10
+ return path.resolve(baseDir, filename.replace(/^\/+/, ""));
11
+ }
12
+ function createFsIo(root) {
13
+ const stdout = process.stdout;
14
+ const stderr = process.stderr;
15
+ return {
16
+ stdout,
17
+ stderr,
18
+ async readFile(filename, baseDir) {
19
+ const full = resolvePath(filename, typeof baseDir === "string" ? baseDir : root);
20
+ try {
21
+ const buf = await fsp.readFile(full);
22
+ return buf.toString("utf8");
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ },
28
+ async writeFile(filename, contents, baseDir) {
29
+ const full = resolvePath(filename, baseDir || root);
30
+ await fsp.mkdir(path.dirname(full), { recursive: true });
31
+ if (typeof contents === "string") {
32
+ await fsp.writeFile(full, contents, "utf8");
33
+ }
34
+ else {
35
+ await fsp.writeFile(full, Buffer.from(contents));
36
+ }
37
+ },
38
+ exsistSync(filename) {
39
+ const full = resolvePath(filename, root);
40
+ const exsists = existsSync(full);
41
+ console.log("checking exists:", full, filename, exsists);
42
+ return existsSync(full);
43
+ },
44
+ async listFiles(dirname, baseDir) {
45
+ const full = resolvePath(dirname, baseDir || root);
46
+ try {
47
+ const entries = await fsp.readdir(full, { withFileTypes: true });
48
+ return entries.map(e => e.name);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ },
54
+ reportDiagnostic(d) {
55
+ stderr.write(String(d) + "\n");
56
+ }
57
+ };
58
+ }
59
+ export async function compilePebbleProject(opts) {
60
+ const { root, entry, outDir, output, config } = opts;
61
+ const io = createFsIo(root);
62
+ const compiler = new Compiler(io, config);
63
+ // Mirror test behavior: pass overrides via compile()
64
+ await compiler.compile({
65
+ root,
66
+ entry,
67
+ outDir
68
+ });
69
+ if (output && output !== "./out.flat") {
70
+ const generated = path.resolve(root, outDir, "out.flat");
71
+ const target = path.isAbsolute(output) ? output : path.resolve(root, output);
72
+ await fsp.mkdir(path.dirname(target), { recursive: true });
73
+ await fsp.copyFile(generated, target);
74
+ }
75
+ }
76
+ export default compilePebbleProject;
@@ -0,0 +1,19 @@
1
+ import { CompilerOptions } from "@harmoniclabs/pebble";
2
+ export interface CliCompileFlags {
3
+ config?: string;
4
+ entry?: string;
5
+ output?: string;
6
+ }
7
+ export interface CliCompileOptions {
8
+ root: string;
9
+ entry: string;
10
+ outDir: string;
11
+ output?: string;
12
+ config: CompilerOptions;
13
+ configPath?: string;
14
+ }
15
+ export declare function normalizeRoot(root?: string): string;
16
+ export declare function isRecord(x: any): x is Record<string, unknown>;
17
+ export declare function fromJsonMaybeBoolean<T>(value: any, fallback: T): T;
18
+ export declare function completeCompileOptions(flags: CliCompileFlags): CliCompileOptions;
19
+ export default completeCompileOptions;
@@ -0,0 +1,50 @@
1
+ import { defaultOptions } from "@harmoniclabs/pebble";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import * as path from "node:path";
4
+ export function normalizeRoot(root) {
5
+ const r = root && root.trim().length > 0 ? root : process.cwd();
6
+ return path.resolve(r);
7
+ }
8
+ export function isRecord(x) {
9
+ return typeof x === "object" && x !== null && !Array.isArray(x);
10
+ }
11
+ export function fromJsonMaybeBoolean(value, fallback) {
12
+ return (typeof value === typeof fallback ? value : fallback);
13
+ }
14
+ export function completeCompileOptions(flags) {
15
+ const root = normalizeRoot();
16
+ const configPath = path.resolve(root, flags.config ?? "./pebble.config.json");
17
+ let config = defaultOptions;
18
+ if (existsSync(configPath)) {
19
+ try {
20
+ const txt = readFileSync(configPath, "utf8");
21
+ const parsed = JSON.parse(txt);
22
+ if (isRecord(parsed))
23
+ config = {
24
+ ...defaultOptions,
25
+ ...parsed,
26
+ };
27
+ }
28
+ catch {
29
+ // ignore malformed config; proceed with flags/defaults
30
+ }
31
+ }
32
+ const cfgEntry = typeof config?.entry === "string" ? String(config.entry) : undefined;
33
+ const entry = cfgEntry ?? (flags.entry ?? "./src/index.pebble");
34
+ const desiredOutput = flags.output;
35
+ const cfgOutDir = typeof config?.outDir === "string" ? String(config.outDir) : undefined;
36
+ let outDir = cfgOutDir ?? (desiredOutput ? path.dirname(desiredOutput) : "./out");
37
+ if (desiredOutput === "./out.flat") {
38
+ // keep sane default when user didn't set anything explicitly
39
+ outDir = cfgOutDir ?? "./out";
40
+ }
41
+ return {
42
+ root,
43
+ entry,
44
+ outDir,
45
+ output: desiredOutput,
46
+ config,
47
+ configPath: existsSync(configPath) ? configPath : undefined,
48
+ };
49
+ }
50
+ export default completeCompileOptions;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node --max-old-space-size=8192
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node --max-old-space-size=8192
2
+ import { Command } from "commander";
3
+ import { PEBBLE_VERSION, PEBBLE_LIB_VERSION } from "./version.generated.js";
4
+ import { initPebbleProject } from "./init/initPebbleProject.js";
5
+ import { compilePebbleProject } from "./compile/compilePebbleProject.js";
6
+ import { completeCompileOptions } from "./compile/completeCompileOptions.js";
7
+ const program = new Command();
8
+ const versionOutput = (`pebble-cli version: ${PEBBLE_VERSION}
9
+ pebble language version: ${PEBBLE_LIB_VERSION}`);
10
+ program
11
+ .name("pebble")
12
+ .description("A simple, yet rock solid, functional language with an imperative bias, targeting UPLC")
13
+ .option("-v, --version", "show version number")
14
+ .action((opts) => {
15
+ if (opts.version) {
16
+ console.log(versionOutput);
17
+ process.exit(0);
18
+ }
19
+ program.help();
20
+ });
21
+ program.command("help")
22
+ .description("Display help for pebble commands")
23
+ .action(() => {
24
+ program.help();
25
+ });
26
+ program.command("version")
27
+ .description("Output the version number")
28
+ .action(() => {
29
+ console.log(versionOutput);
30
+ process.exit(0);
31
+ });
32
+ program.command("compile")
33
+ .description("Compiles a pebble project")
34
+ .option("-c, --config <string>", "The config file path", "./pebble.config.json")
35
+ .option("--entry <string>", "The entry file path .pebble file (used only if missing in the configuration)", "./index.pebble")
36
+ .option("-o, --output <string>", "The output file path (used only if missing in the configuration)", "./out.flat")
37
+ .action(async (opts) => {
38
+ await compilePebbleProject(completeCompileOptions(opts));
39
+ });
40
+ program.command("init")
41
+ .description("Creates a new directory with a fresh pebble project")
42
+ .action(initPebbleProject);
43
+ /*
44
+ // TODO
45
+ defineVersionManager( program );
46
+
47
+ program.command("repl");
48
+ //*/
49
+ program.parse();
@@ -0,0 +1 @@
1
+ export declare function initPebbleProject(): Promise<void>;
@@ -0,0 +1,236 @@
1
+ /// @ts-ignore Cannot find module '@inquirer/prompts' or its corresponding type declarations.
2
+ import { input, confirm, select, Separator } from "@inquirer/prompts";
3
+ import chalk from "chalk";
4
+ import * as path from "node:path";
5
+ import * as fs from "node:fs/promises";
6
+ import { existsSync } from "node:fs";
7
+ export async function initPebbleProject() {
8
+ const projectName = await input({
9
+ required: true,
10
+ message: "What is the name of your project?",
11
+ default: "my-pebble-project",
12
+ });
13
+ let offchain = undefined;
14
+ const includeOffchain = await confirm({
15
+ message: "Do you want to include offchain code using buildooor?",
16
+ default: false
17
+ });
18
+ if (includeOffchain) {
19
+ const network = await select({
20
+ message: "Which network would you like to target?",
21
+ default: "preprod",
22
+ choices: [
23
+ {
24
+ name: "mainnet",
25
+ value: "mainnet",
26
+ description: "Cardano mainnet (transactions cost real ADA)."
27
+ },
28
+ {
29
+ name: "preprod",
30
+ value: "preprod",
31
+ description: "pre-production testnet, closest to mainnet."
32
+ },
33
+ {
34
+ name: "preview",
35
+ value: "preview",
36
+ description: "preview testnet, for quick prototyping."
37
+ },
38
+ ]
39
+ });
40
+ const provider = await select({
41
+ message: "Which provider would you like to use?",
42
+ default: "blockfrost",
43
+ choices: [
44
+ {
45
+ name: "Blockfrost",
46
+ value: "blockfrost",
47
+ description: "Blockfrost is a popular Cardano API provider."
48
+ },
49
+ {
50
+ name: "Koios",
51
+ value: "koios",
52
+ description: "Koios is a community-driven Cardano API provider."
53
+ },
54
+ {
55
+ name: "Kupo + Ogmios (Kupmios)",
56
+ value: "kupmios",
57
+ description: "Use Kupo + Ogmios to index the chain yourself.",
58
+ },
59
+ new Separator(),
60
+ {
61
+ name: "Local node",
62
+ value: "local-node",
63
+ description: "Connect to a local node using a unix socket.",
64
+ disabled: "(coming soon)"
65
+ },
66
+ {
67
+ name: "UTxO RPC",
68
+ value: "utxo-rpc",
69
+ description: "Connect to a UTxO RPC server.",
70
+ disabled: "(coming soon)"
71
+ },
72
+ {
73
+ name: "Maestro",
74
+ value: "maestro",
75
+ description: "Maestro is a managed Cardano API provider.",
76
+ disabled: "(coming soon)"
77
+ }
78
+ ]
79
+ });
80
+ offchain = { network: network, provider: provider };
81
+ }
82
+ // paths
83
+ const projectRoot = path.resolve(process.cwd(), projectName);
84
+ const srcDir = path.join(projectRoot, "src");
85
+ const offchainDir = path.join(projectRoot, "offchain");
86
+ // check existing directory state
87
+ let shouldProceed = true;
88
+ if (existsSync(projectRoot)) {
89
+ let shouldProceed = false;
90
+ try {
91
+ const entries = await fs.readdir(projectRoot);
92
+ if (entries.length > 0) {
93
+ shouldProceed = await confirm({
94
+ message: `Directory "${projectName}" already exists and is not empty. Continue and overwrite conflicting files?`,
95
+ default: false
96
+ });
97
+ }
98
+ }
99
+ catch { /* ignore */ }
100
+ }
101
+ if (!shouldProceed) {
102
+ console.log(chalk.yellow("Aborted. No files were created."));
103
+ return;
104
+ }
105
+ // ensure directories
106
+ await fs.mkdir(srcDir, { recursive: true });
107
+ if (includeOffchain) {
108
+ await fs.mkdir(offchainDir, { recursive: true });
109
+ }
110
+ // write helpers
111
+ async function writeFile(filePath, content) {
112
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
113
+ await fs.writeFile(filePath, content, { encoding: "utf8" });
114
+ }
115
+ // .gitignore
116
+ const gitignorePath = path.join(projectRoot, ".gitignore");
117
+ const gitignore = [
118
+ "node_modules/",
119
+ "dist/",
120
+ "out/",
121
+ ".env",
122
+ ".DS_Store"
123
+ ].join("\n");
124
+ await writeFile(gitignorePath, gitignore + "\n");
125
+ // README
126
+ const readmePath = path.join(projectRoot, "README.md");
127
+ const readme = `# ${projectName}
128
+
129
+ This is a starter project for a Pebble smart contract.
130
+
131
+ Getting started:
132
+
133
+ - Edit your contract in \`src/index.pebble\`.
134
+ - Compile using the Pebble CLI (once \`pebble compile\` is implemented) or your tooling.
135
+
136
+ Structure:
137
+
138
+ - \`src/index.pebble\`: entry script
139
+ - \`pebble.config.json\`: compiler options
140
+ ${includeOffchain ? "- `offchain/`: optional offchain scaffolding\n" : ""}
141
+ `;
142
+ await writeFile(readmePath, readme);
143
+ // pebble config
144
+ const configPath = path.join(projectRoot, "pebble.config.json");
145
+ const pebbleConfig = {
146
+ entry: "./src/index.pebble",
147
+ outDir: "./out",
148
+ removeTraces: true,
149
+ };
150
+ await writeFile(configPath, JSON.stringify(pebbleConfig, null, 2) + "\n");
151
+ // package.json (optional helper scripts)
152
+ const pkgPath = path.join(projectRoot, "package.json");
153
+ const pkgJson = {
154
+ name: projectName,
155
+ private: true,
156
+ type: "module",
157
+ scripts: {
158
+ // placeholders until compile subcommand is wired
159
+ "compile": "pebble compile --config ./pebble.config.json"
160
+ }
161
+ };
162
+ await writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + "\n");
163
+ // src/index.pebble template
164
+ const indexPebblePath = path.join(srcDir, "index.pebble");
165
+ const indexPebble = (`
166
+ // if no methods are defined
167
+ // a simple always failing contract is generated
168
+ contract MyContract {
169
+
170
+ param owner: PubKeyHash;
171
+
172
+ spend ownerAllowsIt() {
173
+ const { tx } = context;
174
+
175
+ assert tx.signatories.includes( this.owner );
176
+ }
177
+
178
+ spend sendToOwner( amount: int ) {
179
+ const { tx } = context;
180
+
181
+ assert tx.outputs.length() === 1;
182
+
183
+ const output = tx.outputs[0];
184
+
185
+ assert output.address.credential.hash() == this.owner;
186
+ assert output.value.lovelaces() >= amount;
187
+ }
188
+
189
+ // mint mintOrBurnTokens() {}
190
+
191
+ // cert validateCertificateSubmission() {}
192
+
193
+ // withdraw getStakingRewards() {}
194
+
195
+ // vote voteOnProposal() {}
196
+
197
+ // propose proposeGovernanceAction () {}
198
+ }`);
199
+ await writeFile(indexPebblePath, indexPebble);
200
+ // optional offchain scaffolding
201
+ if (includeOffchain && offchain) {
202
+ const offchainReadme = `# Offchain
203
+
204
+ This folder is intended for offchain code (e.g., using Buildooor).
205
+
206
+ Selected network: ${offchain.network}
207
+ Selected provider: ${offchain.provider}
208
+
209
+ Configure environment by copying \`.env.example\` to \`.env\` and filling values.
210
+ `;
211
+ await writeFile(path.join(offchainDir, "README.md"), offchainReadme);
212
+ const envLines = [];
213
+ envLines.push(`# Environment variables for ${offchain.provider} on ${offchain.network}`);
214
+ if (offchain.provider === "blockfrost") {
215
+ envLines.push(`BLOCKFROST_API_KEY="your_blockfrost_api_key"`);
216
+ }
217
+ else if (offchain.provider === "koios") {
218
+ envLines.push("KOIOS_URL=https://api.koios.rest/api/v1/");
219
+ }
220
+ else if (offchain.provider === "kupmios") {
221
+ envLines.push("OGMIOS_URL=ws://localhost:1337");
222
+ envLines.push("KUPO_URL=http://localhost:1442");
223
+ }
224
+ await writeFile(path.join(projectRoot, ".env"), envLines.join("\n") + "\n");
225
+ }
226
+ console.log();
227
+ console.log(chalk.green("Pebble project initialized!"));
228
+ console.log(chalk.gray(projectRoot));
229
+ console.log();
230
+ console.log(chalk.green("Next steps:"));
231
+ console.log(` 1. cd ${chalk.cyan(projectName)}`);
232
+ console.log(" 2. Open src/index.pebble and start coding.");
233
+ if (includeOffchain) {
234
+ console.log(" 3. Fill values for your provider in the .env file.");
235
+ }
236
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function defineVersionManager(program: Command): void;
@@ -0,0 +1,33 @@
1
+ export function defineVersionManager(program) {
2
+ const version_manager = program.command("version-manager");
3
+ version_manager.command("list")
4
+ .description("List available pebble versions")
5
+ .action(async () => {
6
+ console.log("Not implemented yet");
7
+ process.exit(0);
8
+ });
9
+ version_manager.command("install <version>")
10
+ .description("Install a specific version of pebble")
11
+ .action(async (version) => {
12
+ console.log("Not implemented yet");
13
+ process.exit(0);
14
+ });
15
+ version_manager.command("use <version>")
16
+ .description("Install and use a specific version of pebble")
17
+ .action(async (version) => {
18
+ console.log("Not implemented yet");
19
+ process.exit(0);
20
+ });
21
+ version_manager.command("remove-all")
22
+ .description("Remove all installed pebble versions")
23
+ .action(async () => {
24
+ console.log("Not implemented yet");
25
+ process.exit(0);
26
+ });
27
+ version_manager.command("select")
28
+ .description("Select a pebble version between the already installed ones")
29
+ .action(async () => {
30
+ console.log("Not implemented yet");
31
+ process.exit(0);
32
+ });
33
+ }
@@ -0,0 +1,2 @@
1
+ export declare const PEBBLE_VERSION = "0.1.0-dev1";
2
+ export declare const PEBBLE_LIB_VERSION = "0.1.0-dev6";
@@ -0,0 +1,3 @@
1
+ // This file is auto-generated by scripts/genVersions.js. Do not edit.
2
+ export const PEBBLE_VERSION = "0.1.0-dev1";
3
+ export const PEBBLE_LIB_VERSION = "0.1.0-dev6";
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@harmoniclabs/pebble-cli",
3
+ "version": "0.1.0-dev1",
4
+ "description": "A simple, yet rock solid, functional language with an imperative bias, targeting UPLC",
5
+ "bin": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "engines": {
8
+ "node": ">=22",
9
+ "bun": ">=1.2.0"
10
+ },
11
+ "type": "module",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "buidl": "npm run build",
17
+ "build": "rm -rf ./dist && npm run build:light",
18
+ "genVersions": "node ./scripts/genVersions.js",
19
+ "build:light": "npm run --silent genVersions && tsc --project ./tsconfig.json && tsc-alias -p ./tsconfig.json",
20
+ "start:light": "node --max-old-space-size=8192 ./dist/index.js",
21
+ "start": "npm run build && npm run start:light",
22
+ "build-exe": "bun run genVersions && bun build src/index.ts --compile --production --target=node --outfile=out/pebble --packages=bundle"
23
+ },
24
+ "publishConfig": {
25
+ "registry": "https://registry.npmjs.org"
26
+ },
27
+ "keywords": [
28
+ "cardano",
29
+ "plutus",
30
+ "smart contracts",
31
+ "transaction",
32
+ "blockchain",
33
+ "dApp"
34
+ ],
35
+ "author": "Michele Nuzzi",
36
+ "license": "Apache-2.0",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/HarmonicLabs/pebble.git"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/HarmonicLabs/pebble/issues"
43
+ },
44
+ "homepage": "https://github.com/HarmonicLabs/pebble#readme",
45
+ "dependencies": {
46
+ "@harmoniclabs/crypto": "^0.3.0",
47
+ "@harmoniclabs/obj-utils": "^1.0.0",
48
+ "@harmoniclabs/pebble": "^0.1.0-dev6",
49
+ "@harmoniclabs/plutus-machine": "^2.1.1",
50
+ "@harmoniclabs/uint8array-utils": "^1.0.4",
51
+ "@harmoniclabs/uplc": "^1.4.0",
52
+ "@inquirer/prompts": "^7.8.4",
53
+ "chalk": "^5.6.0",
54
+ "commander": "^14.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/jest": "^28.1.4",
58
+ "@types/node": "^18.14.6",
59
+ "tsc-alias": "^1.7.1",
60
+ "typescript": "^4.6.3"
61
+ },
62
+ "funding": "https://github.com/sponsors/HarmonicLabs"
63
+ }