@_davideast/jules-env 0.1.2 → 0.2.1

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 (2) hide show
  1. package/dist/cli.mjs +381 -17
  2. package/package.json +7 -2
package/dist/cli.mjs CHANGED
@@ -6029,9 +6029,10 @@ import { spawn } from "node:child_process";
6029
6029
  import { mkdir, writeFile, appendFile } from "node:fs/promises";
6030
6030
  import { resolve, dirname } from "node:path";
6031
6031
  import { homedir } from "node:os";
6032
- async function executePlan(plan, dryRun) {
6032
+ var SHELLENV_SOURCE = ". $HOME/.jules/shellenv 2>/dev/null; ";
6033
+ async function executePlan(plan, dryRun, label) {
6033
6034
  if (dryRun) {
6034
- console.log("--- DRY RUN: Execution Plan ---");
6035
+ console.log(`--- DRY RUN: ${label ?? "Execution Plan"} ---`);
6035
6036
  }
6036
6037
  for (const step of plan.installSteps) {
6037
6038
  if (dryRun) {
@@ -6044,7 +6045,8 @@ async function executePlan(plan, dryRun) {
6044
6045
  console.log(`[${step.id}] ${step.label}...`);
6045
6046
  let skip = false;
6046
6047
  if (step.checkCmd) {
6047
- const check = spawn("sh", ["-c", step.checkCmd], {
6048
+ const fullCheckCmd = `${SHELLENV_SOURCE}${step.checkCmd}`;
6049
+ const check = spawn("sh", ["-c", fullCheckCmd], {
6048
6050
  stdio: "ignore"
6049
6051
  });
6050
6052
  const exitCode = await new Promise((res) => check.on("close", (code) => res(code ?? 1)));
@@ -6054,7 +6056,8 @@ async function executePlan(plan, dryRun) {
6054
6056
  }
6055
6057
  }
6056
6058
  if (!skip) {
6057
- const proc = spawn("sh", ["-c", step.cmd], {
6059
+ const fullCmd = `${SHELLENV_SOURCE}${step.cmd}`;
6060
+ const proc = spawn("sh", ["-c", fullCmd], {
6058
6061
  stdio: "inherit"
6059
6062
  });
6060
6063
  const exitCode = await new Promise((res) => proc.on("close", (code) => res(code ?? 1)));
@@ -6099,6 +6102,54 @@ async function executePlan(plan, dryRun) {
6099
6102
  }
6100
6103
  }
6101
6104
 
6105
+ // src/core/resolver.ts
6106
+ class CircularDependencyError extends Error {
6107
+ chain;
6108
+ constructor(chain) {
6109
+ super(`Circular dependency detected: ${chain.join(" -> ")}`);
6110
+ this.chain = chain;
6111
+ this.name = "CircularDependencyError";
6112
+ }
6113
+ }
6114
+
6115
+ class MissingDependencyError extends Error {
6116
+ from;
6117
+ missing;
6118
+ constructor(from, missing) {
6119
+ super(`Recipe '${from}' depends on missing recipe '${missing}'`);
6120
+ this.from = from;
6121
+ this.missing = missing;
6122
+ this.name = "MissingDependencyError";
6123
+ }
6124
+ }
6125
+ function resolveDependencies(recipeName, registry) {
6126
+ const result = [];
6127
+ const visited = new Set;
6128
+ const visiting = new Set;
6129
+ function walk(name, chain) {
6130
+ if (visited.has(name))
6131
+ return;
6132
+ if (visiting.has(name))
6133
+ throw new CircularDependencyError([...chain, name]);
6134
+ const recipe = registry[name];
6135
+ if (!recipe) {
6136
+ if (chain.length > 0) {
6137
+ throw new MissingDependencyError(chain[chain.length - 1], name);
6138
+ }
6139
+ throw new Error(`Recipe '${name}' not found`);
6140
+ }
6141
+ visiting.add(name);
6142
+ for (const dep of recipe.depends ?? []) {
6143
+ walk(dep, [...chain, name]);
6144
+ }
6145
+ visiting.delete(name);
6146
+ visited.add(name);
6147
+ result.push(name);
6148
+ }
6149
+ walk(recipeName, []);
6150
+ return result;
6151
+ }
6152
+
6102
6153
  // src/recipes/dart.ts
6103
6154
  import { spawnSync } from "node:child_process";
6104
6155
  async function resolveDarwin() {
@@ -6176,6 +6227,304 @@ var DartRecipe = {
6176
6227
  }
6177
6228
  };
6178
6229
 
6230
+ // src/recipes/flutter.ts
6231
+ import { spawnSync as spawnSync2 } from "node:child_process";
6232
+ async function resolveDarwin2() {
6233
+ const installSteps = [
6234
+ {
6235
+ id: "install-flutter",
6236
+ label: "Install Flutter SDK",
6237
+ cmd: "brew install --cask flutter",
6238
+ checkCmd: "brew list --cask flutter"
6239
+ },
6240
+ {
6241
+ id: "precache-web",
6242
+ label: "Precache Flutter web artifacts",
6243
+ cmd: "flutter precache --web",
6244
+ checkCmd: 'test -d "$(brew --cask --room 2>/dev/null || echo /usr/local/Caskroom)/flutter"/*/flutter/bin/cache/flutter_web_sdk'
6245
+ }
6246
+ ];
6247
+ let flutterRoot = "";
6248
+ try {
6249
+ const result = spawnSync2("brew", ["--cask", "--room"], { encoding: "utf-8" });
6250
+ if (result.status === 0) {
6251
+ const caskroom = result.stdout.trim();
6252
+ const ls = spawnSync2("ls", [caskroom + "/flutter"], { encoding: "utf-8" });
6253
+ if (ls.status === 0) {
6254
+ const version = ls.stdout.trim().split(`
6255
+ `)[0];
6256
+ if (version) {
6257
+ flutterRoot = `${caskroom}/flutter/${version}/flutter`;
6258
+ }
6259
+ }
6260
+ }
6261
+ } catch (e) {}
6262
+ if (!flutterRoot) {
6263
+ flutterRoot = "/usr/local/Caskroom/flutter/latest/flutter";
6264
+ }
6265
+ installSteps[1].checkCmd = `test -d ${flutterRoot}/bin/cache/flutter_web_sdk`;
6266
+ const env = {
6267
+ FLUTTER_ROOT: flutterRoot
6268
+ };
6269
+ const paths = [
6270
+ `${flutterRoot}/bin`
6271
+ ];
6272
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6273
+ }
6274
+ async function resolveLinux2() {
6275
+ const installSteps = [
6276
+ {
6277
+ id: "install-flutter-prereqs",
6278
+ label: "Install prerequisites",
6279
+ cmd: "sudo apt-get update && sudo apt-get install -y curl git unzip xz-utils",
6280
+ checkCmd: "dpkg -s curl && dpkg -s git && dpkg -s unzip && dpkg -s xz-utils"
6281
+ },
6282
+ {
6283
+ id: "clone-flutter",
6284
+ label: "Clone Flutter SDK",
6285
+ cmd: "sudo git clone -b stable https://github.com/flutter/flutter.git /usr/local/flutter && sudo chown -R $(id -u):$(id -g) /usr/local/flutter",
6286
+ checkCmd: "test -d /usr/local/flutter"
6287
+ },
6288
+ {
6289
+ id: "precache-web",
6290
+ label: "Precache Flutter web artifacts",
6291
+ cmd: "/usr/local/flutter/bin/flutter precache --web",
6292
+ checkCmd: "test -d /usr/local/flutter/bin/cache/flutter_web_sdk"
6293
+ }
6294
+ ];
6295
+ const env = {
6296
+ FLUTTER_ROOT: "/usr/local/flutter"
6297
+ };
6298
+ const paths = [
6299
+ "/usr/local/flutter/bin"
6300
+ ];
6301
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6302
+ }
6303
+ var FlutterRecipe = {
6304
+ name: "flutter",
6305
+ description: "Flutter SDK (web)",
6306
+ resolve: async (ctx) => {
6307
+ switch (process.platform) {
6308
+ case "darwin":
6309
+ return resolveDarwin2();
6310
+ case "linux":
6311
+ return resolveLinux2();
6312
+ default:
6313
+ throw new Error(`Unsupported platform: ${process.platform}`);
6314
+ }
6315
+ }
6316
+ };
6317
+
6318
+ // src/recipes/ruby.ts
6319
+ import { spawnSync as spawnSync3 } from "node:child_process";
6320
+ async function resolveDarwin3() {
6321
+ const installSteps = [{
6322
+ id: "install-ruby",
6323
+ label: "Install Ruby",
6324
+ cmd: "brew install ruby",
6325
+ checkCmd: "brew list --versions ruby"
6326
+ }];
6327
+ let rubyPrefix = "";
6328
+ try {
6329
+ const result = spawnSync3("brew", ["--prefix", "ruby"], { encoding: "utf-8" });
6330
+ if (result.status === 0) {
6331
+ rubyPrefix = result.stdout.trim();
6332
+ }
6333
+ } catch (e) {}
6334
+ if (!rubyPrefix) {
6335
+ rubyPrefix = "/usr/local/opt/ruby";
6336
+ }
6337
+ const env = {
6338
+ GEM_HOME: "$HOME/.gem/ruby"
6339
+ };
6340
+ const paths = [
6341
+ `${rubyPrefix}/bin`,
6342
+ "$HOME/.gem/ruby/bin"
6343
+ ];
6344
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6345
+ }
6346
+ async function resolveLinux3() {
6347
+ const installSteps = [
6348
+ {
6349
+ id: "install-ruby-prereqs",
6350
+ label: "Install build prerequisites",
6351
+ cmd: "sudo apt-get update && sudo apt-get install -y build-essential",
6352
+ checkCmd: "dpkg -s build-essential"
6353
+ },
6354
+ {
6355
+ id: "install-ruby",
6356
+ label: "Install Ruby",
6357
+ cmd: "sudo apt-get install -y ruby-full",
6358
+ checkCmd: "dpkg -s ruby-full"
6359
+ }
6360
+ ];
6361
+ const env = {
6362
+ GEM_HOME: "$HOME/.gem/ruby"
6363
+ };
6364
+ const paths = [
6365
+ "$HOME/.gem/ruby/bin"
6366
+ ];
6367
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6368
+ }
6369
+ var RubyRecipe = {
6370
+ name: "ruby",
6371
+ description: "Ruby programming language",
6372
+ resolve: async (ctx) => {
6373
+ switch (process.platform) {
6374
+ case "darwin":
6375
+ return resolveDarwin3();
6376
+ case "linux":
6377
+ return resolveLinux3();
6378
+ default:
6379
+ throw new Error(`Unsupported platform: ${process.platform}`);
6380
+ }
6381
+ }
6382
+ };
6383
+
6384
+ // src/recipes/php.ts
6385
+ import { spawnSync as spawnSync4 } from "node:child_process";
6386
+ async function resolveDarwin4() {
6387
+ const installSteps = [
6388
+ {
6389
+ id: "install-php",
6390
+ label: "Install PHP",
6391
+ cmd: "brew install php",
6392
+ checkCmd: "brew list --versions php"
6393
+ },
6394
+ {
6395
+ id: "install-composer",
6396
+ label: "Install Composer",
6397
+ cmd: "brew install composer",
6398
+ checkCmd: "brew list --versions composer"
6399
+ }
6400
+ ];
6401
+ let phpPrefix = "";
6402
+ try {
6403
+ const result = spawnSync4("brew", ["--prefix", "php"], { encoding: "utf-8" });
6404
+ if (result.status === 0) {
6405
+ phpPrefix = result.stdout.trim();
6406
+ }
6407
+ } catch (e) {}
6408
+ if (!phpPrefix) {
6409
+ phpPrefix = "/usr/local/opt/php";
6410
+ }
6411
+ const env = {
6412
+ COMPOSER_HOME: "$HOME/.config/composer"
6413
+ };
6414
+ const paths = [
6415
+ `${phpPrefix}/bin`,
6416
+ "$HOME/.config/composer/vendor/bin"
6417
+ ];
6418
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6419
+ }
6420
+ async function resolveLinux4() {
6421
+ const installSteps = [
6422
+ {
6423
+ id: "install-php",
6424
+ label: "Install PHP",
6425
+ cmd: "sudo apt-get update && sudo apt-get install -y php-cli php-common php-mbstring php-xml php-curl php-zip unzip",
6426
+ checkCmd: "dpkg -s php-cli"
6427
+ },
6428
+ {
6429
+ id: "install-composer",
6430
+ label: "Install Composer",
6431
+ cmd: "curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer",
6432
+ checkCmd: "which composer"
6433
+ }
6434
+ ];
6435
+ const env = {
6436
+ COMPOSER_HOME: "$HOME/.config/composer"
6437
+ };
6438
+ const paths = [
6439
+ "$HOME/.config/composer/vendor/bin"
6440
+ ];
6441
+ return ExecutionPlanSchema.parse({ installSteps, env, paths });
6442
+ }
6443
+ var PhpRecipe = {
6444
+ name: "php",
6445
+ description: "PHP programming language with Composer",
6446
+ resolve: async (ctx) => {
6447
+ switch (process.platform) {
6448
+ case "darwin":
6449
+ return resolveDarwin4();
6450
+ case "linux":
6451
+ return resolveLinux4();
6452
+ default:
6453
+ throw new Error(`Unsupported platform: ${process.platform}`);
6454
+ }
6455
+ }
6456
+ };
6457
+
6458
+ // src/recipes/php-sqlite.ts
6459
+ async function resolveDarwin5() {
6460
+ return ExecutionPlanSchema.parse({ installSteps: [], env: {}, paths: [] });
6461
+ }
6462
+ async function resolveLinux5() {
6463
+ const installSteps = [
6464
+ {
6465
+ id: "install-php-sqlite",
6466
+ label: "Install PHP SQLite extension",
6467
+ cmd: "sudo apt-get update && sudo apt-get install -y php-sqlite3",
6468
+ checkCmd: "dpkg -s php-sqlite3"
6469
+ }
6470
+ ];
6471
+ return ExecutionPlanSchema.parse({ installSteps, env: {}, paths: [] });
6472
+ }
6473
+ var PhpSqliteRecipe = {
6474
+ name: "php-sqlite",
6475
+ description: "PHP SQLite extension",
6476
+ depends: ["php"],
6477
+ resolve: async (ctx) => {
6478
+ switch (process.platform) {
6479
+ case "darwin":
6480
+ return resolveDarwin5();
6481
+ case "linux":
6482
+ return resolveLinux5();
6483
+ default:
6484
+ throw new Error(`Unsupported platform: ${process.platform}`);
6485
+ }
6486
+ }
6487
+ };
6488
+
6489
+ // src/recipes/laravel.ts
6490
+ async function resolveDarwin6() {
6491
+ const installSteps = [
6492
+ {
6493
+ id: "install-laravel-installer",
6494
+ label: "Install Laravel installer",
6495
+ cmd: "composer global require laravel/installer",
6496
+ checkCmd: "laravel --version"
6497
+ }
6498
+ ];
6499
+ return ExecutionPlanSchema.parse({ installSteps, env: {}, paths: [] });
6500
+ }
6501
+ async function resolveLinux6() {
6502
+ const installSteps = [
6503
+ {
6504
+ id: "install-laravel-installer",
6505
+ label: "Install Laravel installer",
6506
+ cmd: "composer global require laravel/installer",
6507
+ checkCmd: "laravel --version"
6508
+ }
6509
+ ];
6510
+ return ExecutionPlanSchema.parse({ installSteps, env: {}, paths: [] });
6511
+ }
6512
+ var LaravelRecipe = {
6513
+ name: "laravel",
6514
+ description: "Laravel PHP framework installer",
6515
+ depends: ["php-sqlite"],
6516
+ resolve: async (ctx) => {
6517
+ switch (process.platform) {
6518
+ case "darwin":
6519
+ return resolveDarwin6();
6520
+ case "linux":
6521
+ return resolveLinux6();
6522
+ default:
6523
+ throw new Error(`Unsupported platform: ${process.platform}`);
6524
+ }
6525
+ }
6526
+ };
6527
+
6179
6528
  // src/core/loader.ts
6180
6529
  function substituteVars(str, vars) {
6181
6530
  return str.replace(/\{\{(\w+)\}\}/g, (_, key) => {
@@ -6232,13 +6581,13 @@ var ollama_default = {
6232
6581
  {
6233
6582
  id: "enable-ollama",
6234
6583
  label: "Enable Ollama service",
6235
- cmd: "sudo systemctl daemon-reload && sudo systemctl enable --now ollama",
6236
- checkCmd: "systemctl is-active ollama"
6584
+ cmd: "if command -v systemctl >/dev/null 2>&1; then sudo systemctl daemon-reload && sudo systemctl enable --now ollama; else nohup ollama serve > /tmp/ollama.log 2>&1 & fi",
6585
+ checkCmd: "curl -sf http://localhost:11434/ >/dev/null 2>&1"
6237
6586
  },
6238
6587
  {
6239
6588
  id: "wait-for-ollama",
6240
6589
  label: "Wait for Ollama to initialize",
6241
- cmd: "sleep 5"
6590
+ cmd: "for i in 1 2 3 4 5 6 7 8 9 10; do curl -sf http://localhost:11434/ >/dev/null 2>&1 && exit 0; sleep 1; done; echo 'Ollama server did not start'; exit 1"
6242
6591
  },
6243
6592
  {
6244
6593
  id: "pull-model",
@@ -6255,7 +6604,7 @@ var ollama_default = {
6255
6604
  // package.json
6256
6605
  var package_default = {
6257
6606
  name: "@_davideast/jules-env",
6258
- version: "0.1.2",
6607
+ version: "0.2.1",
6259
6608
  description: "Configure ephemeral development environments",
6260
6609
  license: "Apache-2.0",
6261
6610
  type: "module",
@@ -6288,7 +6637,12 @@ var package_default = {
6288
6637
  test: "bun test",
6289
6638
  typecheck: "tsc --noEmit",
6290
6639
  "check:all": "bun run scripts/prepublish-checks.ts",
6291
- prepack: "bun run scripts/prepublish-checks.ts"
6640
+ prepack: "bun run scripts/prepublish-checks.ts",
6641
+ docker: "docker build -t jules-env . && docker run --rm -it jules-env",
6642
+ "test:install": "./scripts/test-install.sh"
6643
+ },
6644
+ np: {
6645
+ "2fa": false
6292
6646
  }
6293
6647
  };
6294
6648
 
@@ -6296,28 +6650,38 @@ var package_default = {
6296
6650
  var program2 = new Command;
6297
6651
  var recipes = {
6298
6652
  dart: DartRecipe,
6653
+ flutter: FlutterRecipe,
6654
+ ruby: RubyRecipe,
6655
+ php: PhpRecipe,
6656
+ "php-sqlite": PhpSqliteRecipe,
6657
+ laravel: LaravelRecipe,
6299
6658
  ollama: loadDataRecipe(ollama_default)
6300
6659
  };
6301
6660
  program2.name("jules-env").description("Configure ephemeral development environments").version(package_default.version);
6302
6661
  program2.command("use <runtime>").description("Setup a runtime environment").option("--version <v>", "Version to install", "latest").option("--dry-run", "Simulate execution", false).option("--preset <p>", "Configuration preset").action(async (runtime, options) => {
6303
6662
  try {
6304
- const recipe = recipes[runtime];
6305
- if (!recipe) {
6306
- console.error(`Error: Recipe for '${runtime}' not found.`);
6307
- process.exit(1);
6308
- }
6309
6663
  const context = UseContextSchema.parse({
6310
6664
  runtime,
6311
6665
  version: options.version,
6312
6666
  preset: options.preset,
6313
6667
  dryRun: options.dryRun
6314
6668
  });
6315
- console.log(`[jules-env] Resolving plan for ${runtime}...`);
6316
- const plan = await recipe.resolve(context);
6317
- await executePlan(plan, context.dryRun);
6669
+ const order = resolveDependencies(runtime, recipes);
6670
+ if (order.length > 1) {
6671
+ console.log(`[jules-env] Resolving dependencies: ${order.join(" -> ")}`);
6672
+ }
6673
+ for (const recipeName of order) {
6674
+ const recipe = recipes[recipeName];
6675
+ const depContext = recipeName === runtime ? context : UseContextSchema.parse({ runtime: recipeName, dryRun: context.dryRun });
6676
+ console.log(`[jules-env] Resolving plan for ${recipeName}...`);
6677
+ const plan = await recipe.resolve(depContext);
6678
+ await executePlan(plan, context.dryRun, recipeName);
6679
+ }
6318
6680
  } catch (err) {
6319
6681
  if (err instanceof z.ZodError) {
6320
6682
  console.error("Validation Error:", err.errors);
6683
+ } else if (err instanceof CircularDependencyError || err instanceof MissingDependencyError) {
6684
+ console.error("Dependency Error:", err.message);
6321
6685
  } else {
6322
6686
  console.error("Error:", err);
6323
6687
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_davideast/jules-env",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Configure ephemeral development environments",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -33,6 +33,11 @@
33
33
  "test": "bun test",
34
34
  "typecheck": "tsc --noEmit",
35
35
  "check:all": "bun run scripts/prepublish-checks.ts",
36
- "prepack": "bun run scripts/prepublish-checks.ts"
36
+ "prepack": "bun run scripts/prepublish-checks.ts",
37
+ "docker": "docker build -t jules-env . && docker run --rm -it jules-env",
38
+ "test:install": "./scripts/test-install.sh"
39
+ },
40
+ "np": {
41
+ "2fa": false
37
42
  }
38
43
  }