@driftdev/cli 1.13.0 → 1.14.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/README.md CHANGED
@@ -158,7 +158,7 @@ Validate @example blocks.
158
158
  ```bash
159
159
  drift examples # presence check
160
160
  drift examples --typecheck # type-check examples
161
- drift examples --run # execute examples
161
+ drift examples --run # execute examples with // => assertions
162
162
  drift examples --min 50 # fail if example coverage below 50%
163
163
  ```
164
164
 
package/dist/drift.js CHANGED
@@ -737,6 +737,16 @@ function validateConfig(raw) {
737
737
  }
738
738
  }
739
739
  }
740
+ if (obj.examples !== undefined) {
741
+ if (typeof obj.examples !== "object" || obj.examples === null) {
742
+ errors.push('"examples" must be an object');
743
+ } else {
744
+ const examples = obj.examples;
745
+ if (examples.run !== undefined && typeof examples.run !== "boolean") {
746
+ errors.push('"examples.run" must be a boolean');
747
+ }
748
+ }
749
+ }
740
750
  if (errors.length > 0)
741
751
  return { ok: false, errors };
742
752
  const { $schema: _schema, ...rest } = obj;
@@ -3053,14 +3063,15 @@ function renderExamples(data) {
3053
3063
  lines.push("");
3054
3064
  }
3055
3065
  if (data.run) {
3056
- const { passed, failed, drifts, installSuccess, installError } = data.run;
3066
+ const { passed, failed, skipped, drifts, installSuccess, installError } = data.run;
3057
3067
  if (!installSuccess) {
3058
- lines.push(indent(`${c.gray("RUNTIME")} ${c.red("install failed")}`));
3068
+ lines.push(indent(`${c.gray("RUNTIME")} ${c.red("failed")}`));
3059
3069
  if (installError) {
3060
3070
  lines.push(indent(` ${c.red(installError)}`));
3061
3071
  }
3062
3072
  } else {
3063
- const status = failed === 0 ? c.green(`${passed} passed`) : `${c.green(`${passed} passed`)} ${c.red(`${failed} failed`)}`;
3073
+ const skipBit = skipped > 0 ? ` ${c.gray(`${skipped} skipped`)}` : "";
3074
+ const status = failed === 0 ? `${c.green(`${passed} passed`)}${skipBit}` : `${c.green(`${passed} passed`)} ${c.red(`${failed} failed`)}${skipBit}`;
3064
3075
  lines.push(indent(`${c.gray("RUNTIME")} ${status}`));
3065
3076
  if (drifts.length > 0) {
3066
3077
  const shown = drifts.slice(0, 10);
@@ -3079,7 +3090,7 @@ function renderExamples(data) {
3079
3090
  if (!ran.includes("typecheck") && !ran.includes("run")) {
3080
3091
  lines.push(indent(c.gray("Tip: drift examples --typecheck to compile-check examples")));
3081
3092
  } else if (!ran.includes("run")) {
3082
- lines.push(indent(c.gray("Tip: drift examples --run to execute examples at runtime")));
3093
+ lines.push(indent(c.gray("Tip: drift examples --run to execute examples with // => assertions")));
3083
3094
  }
3084
3095
  lines.push("");
3085
3096
  return lines.join(`
@@ -3100,7 +3111,7 @@ function findPackagePath(entryFile) {
3100
3111
  return path22.dirname(entryFile);
3101
3112
  }
3102
3113
  function registerExamplesCommand(program) {
3103
- program.command("examples [entry]").description("Validate @example blocks on exports").option("--typecheck", "Type-check examples with TypeScript").option("--run", "Execute examples at runtime (implies --typecheck)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--min <n>", "Minimum presence threshold (exit 1 if below)").action(async (entry, options) => {
3114
+ program.command("examples [entry]").description("Validate @example blocks on exports").option("--typecheck", "Type-check examples with TypeScript").option("--run", "Execute examples that contain // => assertions (implies --typecheck; installs the package)").option("--yes", "Confirm execution (required when not a TTY)").option("--untrusted", "Treat the target as a third-party package (OS isolation; macOS only)").option("--all", "Run across all workspace packages").option("--private", "Include private packages in --all mode").option("--min <n>", "Minimum presence threshold (exit 1 if below)").action(async (entry, options) => {
3104
3115
  const startTime = Date.now();
3105
3116
  const version = getVersion();
3106
3117
  try {
@@ -3109,9 +3120,13 @@ function registerExamplesCommand(program) {
3109
3120
  validations.push("typecheck");
3110
3121
  if (options.run)
3111
3122
  validations.push("run");
3123
+ const { config } = loadConfig();
3112
3124
  if (options.run) {
3113
- process.stderr.write(`⚠ --run executes code from @example blocks. Only use on trusted code.
3114
- `);
3125
+ const confirmed = Boolean(options.yes) || config.examples?.run === true || Boolean(process.stdin.isTTY);
3126
+ if (!confirmed) {
3127
+ formatError("examples", "--run requires --yes or examples.run: true in config when not running interactively", startTime, version);
3128
+ return;
3129
+ }
3115
3130
  }
3116
3131
  if (options.all) {
3117
3132
  const allPackages = discoverPackages(process.cwd());
@@ -3137,7 +3152,8 @@ function registerExamplesCommand(program) {
3137
3152
  const result2 = await validateExamples(exps, {
3138
3153
  validations,
3139
3154
  packagePath: packagePath2,
3140
- exportNames: exportNames2
3155
+ exportNames: exportNames2,
3156
+ untrusted: options.untrusted
3141
3157
  });
3142
3158
  const withEx = result2.presence?.withExamples ?? 0;
3143
3159
  const total = result2.presence?.total ?? exps.length;
@@ -3147,8 +3163,9 @@ function registerExamplesCommand(program) {
3147
3163
  totalAll += total;
3148
3164
  if (result2.typecheck && result2.typecheck.failed > 0)
3149
3165
  hasFailures = true;
3150
- if (result2.run && result2.run.failed > 0)
3166
+ if (result2.run && (result2.run.failed > 0 || !result2.run.installSuccess)) {
3151
3167
  hasFailures = true;
3168
+ }
3152
3169
  }
3153
3170
  const aggScore = totalAll > 0 ? Math.round(totalWith / totalAll * 100) : 100;
3154
3171
  const data = {
@@ -3164,7 +3181,6 @@ function registerExamplesCommand(program) {
3164
3181
  process.exitCode = 1;
3165
3182
  return;
3166
3183
  }
3167
- const { config } = loadConfig();
3168
3184
  const entryFile = entry ? path22.resolve(process.cwd(), entry) : config.entry ? path22.resolve(process.cwd(), config.entry) : detectEntry();
3169
3185
  const { spec } = await cachedExtract(entryFile);
3170
3186
  const exports = spec.exports ?? [];
@@ -3173,7 +3189,8 @@ function registerExamplesCommand(program) {
3173
3189
  const result = await validateExamples(exports, {
3174
3190
  validations,
3175
3191
  packagePath,
3176
- exportNames
3192
+ exportNames,
3193
+ untrusted: options.untrusted
3177
3194
  });
3178
3195
  const missingCount = result.presence?.missing?.length ?? 0;
3179
3196
  const failedCount = (result.typecheck?.failed ?? 0) + (result.run?.failed ?? 0);
@@ -3191,8 +3208,9 @@ function registerExamplesCommand(program) {
3191
3208
  }
3192
3209
  if (result.typecheck && result.typecheck.failed > 0)
3193
3210
  process.exitCode = 1;
3194
- if (result.run && result.run.failed > 0)
3211
+ if (result.run && (result.run.failed > 0 || !result.run.installSuccess)) {
3195
3212
  process.exitCode = 1;
3213
+ }
3196
3214
  } catch (err) {
3197
3215
  formatError("examples", err instanceof Error ? err.message : String(err), startTime, version);
3198
3216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@driftdev/cli",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Drift CLI - detect when your docs drift from your code",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,7 +47,7 @@
47
47
  "dependencies": {
48
48
  "@driftdev/clarity-adapter": "^1.0.1",
49
49
  "@driftdev/openapi-adapter": "^1.0.1",
50
- "@driftdev/sdk": "^1.13.0",
50
+ "@driftdev/sdk": "^1.14.0",
51
51
  "@modelcontextprotocol/sdk": "^1.29.0",
52
52
  "@openpkg-ts/sdk": "^0.51.0",
53
53
  "@openpkg-ts/spec": "^0.50.0",
@@ -79,6 +79,17 @@
79
79
  }
80
80
  },
81
81
  "additionalProperties": false
82
+ },
83
+ "examples": {
84
+ "type": "object",
85
+ "description": "Example execution policy",
86
+ "properties": {
87
+ "run": {
88
+ "type": "boolean",
89
+ "description": "Allow --run in non-TTY (CI) without --yes"
90
+ }
91
+ },
92
+ "additionalProperties": false
82
93
  }
83
94
  },
84
95
  "additionalProperties": true