@_davideast/jules-env 0.2.0 → 0.2.2

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 +293 -22
  2. package/package.json +3 -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 = "test -f $HOME/.jules/shellenv && . $HOME/.jules/shellenv; ";
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() {
@@ -6131,7 +6182,7 @@ async function resolveLinux() {
6131
6182
  {
6132
6183
  id: "install-dart-prereqs",
6133
6184
  label: "Install prerequisites",
6134
- cmd: "sudo apt-get update && sudo apt-get install -y apt-transport-https wget",
6185
+ cmd: "(sudo apt-get update || true) && sudo apt-get install -y apt-transport-https wget",
6135
6186
  checkCmd: "dpkg -s apt-transport-https && dpkg -s wget"
6136
6187
  },
6137
6188
  {
@@ -6149,7 +6200,7 @@ async function resolveLinux() {
6149
6200
  {
6150
6201
  id: "install-dart",
6151
6202
  label: "Install Dart SDK",
6152
- cmd: "sudo apt-get update && sudo apt-get install -y dart",
6203
+ cmd: "(sudo apt-get update || true) && sudo apt-get install -y dart",
6153
6204
  checkCmd: "dpkg -s dart"
6154
6205
  }
6155
6206
  ];
@@ -6225,13 +6276,13 @@ async function resolveLinux2() {
6225
6276
  {
6226
6277
  id: "install-flutter-prereqs",
6227
6278
  label: "Install prerequisites",
6228
- cmd: "sudo apt-get update && sudo apt-get install -y curl git unzip xz-utils",
6279
+ cmd: "(sudo apt-get update || true) && sudo apt-get install -y curl git unzip xz-utils",
6229
6280
  checkCmd: "dpkg -s curl && dpkg -s git && dpkg -s unzip && dpkg -s xz-utils"
6230
6281
  },
6231
6282
  {
6232
6283
  id: "clone-flutter",
6233
6284
  label: "Clone Flutter SDK",
6234
- cmd: "git clone -b stable https://github.com/flutter/flutter.git /usr/local/flutter",
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",
6235
6286
  checkCmd: "test -d /usr/local/flutter"
6236
6287
  },
6237
6288
  {
@@ -6264,6 +6315,216 @@ var FlutterRecipe = {
6264
6315
  }
6265
6316
  };
6266
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 || true) && 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 || true) && 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 || true) && 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
+
6267
6528
  // src/core/loader.ts
6268
6529
  function substituteVars(str, vars) {
6269
6530
  return str.replace(/\{\{(\w+)\}\}/g, (_, key) => {
@@ -6308,7 +6569,7 @@ var ollama_default = {
6308
6569
  {
6309
6570
  id: "install-zstd",
6310
6571
  label: "Install zstd",
6311
- cmd: "sudo apt-get update && sudo apt-get install -y zstd",
6572
+ cmd: "(sudo apt-get update || true) && sudo apt-get install -y zstd",
6312
6573
  checkCmd: "dpkg -s zstd"
6313
6574
  },
6314
6575
  {
@@ -6320,13 +6581,13 @@ var ollama_default = {
6320
6581
  {
6321
6582
  id: "enable-ollama",
6322
6583
  label: "Enable Ollama service",
6323
- cmd: "sudo systemctl daemon-reload && sudo systemctl enable --now ollama",
6324
- 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"
6325
6586
  },
6326
6587
  {
6327
6588
  id: "wait-for-ollama",
6328
6589
  label: "Wait for Ollama to initialize",
6329
- 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"
6330
6591
  },
6331
6592
  {
6332
6593
  id: "pull-model",
@@ -6343,7 +6604,7 @@ var ollama_default = {
6343
6604
  // package.json
6344
6605
  var package_default = {
6345
6606
  name: "@_davideast/jules-env",
6346
- version: "0.2.0",
6607
+ version: "0.2.2",
6347
6608
  description: "Configure ephemeral development environments",
6348
6609
  license: "Apache-2.0",
6349
6610
  type: "module",
@@ -6377,7 +6638,8 @@ var package_default = {
6377
6638
  typecheck: "tsc --noEmit",
6378
6639
  "check:all": "bun run scripts/prepublish-checks.ts",
6379
6640
  prepack: "bun run scripts/prepublish-checks.ts",
6380
- docker: "docker build -t jules-env . && docker run --rm -it jules-env"
6641
+ docker: "docker build -t jules-env . && docker run --rm -it jules-env",
6642
+ "test:install": "./scripts/test-install.sh"
6381
6643
  },
6382
6644
  np: {
6383
6645
  "2fa": false
@@ -6389,28 +6651,37 @@ var program2 = new Command;
6389
6651
  var recipes = {
6390
6652
  dart: DartRecipe,
6391
6653
  flutter: FlutterRecipe,
6654
+ ruby: RubyRecipe,
6655
+ php: PhpRecipe,
6656
+ "php-sqlite": PhpSqliteRecipe,
6657
+ laravel: LaravelRecipe,
6392
6658
  ollama: loadDataRecipe(ollama_default)
6393
6659
  };
6394
6660
  program2.name("jules-env").description("Configure ephemeral development environments").version(package_default.version);
6395
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) => {
6396
6662
  try {
6397
- const recipe = recipes[runtime];
6398
- if (!recipe) {
6399
- console.error(`Error: Recipe for '${runtime}' not found.`);
6400
- process.exit(1);
6401
- }
6402
6663
  const context = UseContextSchema.parse({
6403
6664
  runtime,
6404
6665
  version: options.version,
6405
6666
  preset: options.preset,
6406
6667
  dryRun: options.dryRun
6407
6668
  });
6408
- console.log(`[jules-env] Resolving plan for ${runtime}...`);
6409
- const plan = await recipe.resolve(context);
6410
- 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
+ }
6411
6680
  } catch (err) {
6412
6681
  if (err instanceof z.ZodError) {
6413
6682
  console.error("Validation Error:", err.errors);
6683
+ } else if (err instanceof CircularDependencyError || err instanceof MissingDependencyError) {
6684
+ console.error("Dependency Error:", err.message);
6414
6685
  } else {
6415
6686
  console.error("Error:", err);
6416
6687
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_davideast/jules-env",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Configure ephemeral development environments",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -34,7 +34,8 @@
34
34
  "typecheck": "tsc --noEmit",
35
35
  "check:all": "bun run scripts/prepublish-checks.ts",
36
36
  "prepack": "bun run scripts/prepublish-checks.ts",
37
- "docker": "docker build -t jules-env . && docker run --rm -it jules-env"
37
+ "docker": "docker build -t jules-env . && docker run --rm -it jules-env",
38
+ "test:install": "./scripts/test-install.sh"
38
39
  },
39
40
  "np": {
40
41
  "2fa": false