@dura-run/cli 0.3.2 → 0.3.3
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 +15 -9
- package/dist/dura.js +88 -9
- package/package.json +1 -1
- package/skills/dura-develop.md +31 -0
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ All project-scoped commands pick up `projectId` from `dura.json` — you only ne
|
|
|
45
45
|
|
|
46
46
|
### `dura dev` runs your handlers in-process (GH #118)
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
`dura dev` loads your automation code via native `import(...)` in the
|
|
49
|
+
CLI's Node process. That process has full access to your filesystem —
|
|
50
|
+
including `~/.dura/config` (where the CLI stores your API key), `~/.ssh`,
|
|
51
|
+
`~/.aws`, and environment variables. A malicious npm dependency anywhere
|
|
52
|
+
in your handler's import graph could read those files and exfiltrate
|
|
53
|
+
them on the first request.
|
|
54
54
|
|
|
55
55
|
To make this risk explicit, `dura dev` refuses to start whenever you
|
|
56
56
|
have credentials stored locally unless you opt in per project:
|
|
@@ -77,9 +77,15 @@ Treat `dura dev` with the same trust you'd give `node -e ...` in a
|
|
|
77
77
|
project that imports every one of your dependencies — audit your
|
|
78
78
|
`package.json` (and lockfile) before trusting a new project.
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
The trust gate is the permanent mitigation. We evaluated a full
|
|
81
|
+
isolated-vm migration for local dev in
|
|
82
|
+
[GH #147](https://github.com/dura-run/dura-run/issues/147) and declined
|
|
83
|
+
it: the threat window is narrow (malicious deps mostly fire at install
|
|
84
|
+
or require-time, which sandboxing `dura dev` wouldn't help with), and
|
|
85
|
+
the engineering cost is high relative to the mitigation. Production
|
|
86
|
+
handlers run in a real V8 isolate via the executor service. See
|
|
87
|
+
[docs/internal/architecture.md](../../docs/internal/architecture.md) for
|
|
88
|
+
the threat-model decision.
|
|
83
89
|
|
|
84
90
|
## Documentation
|
|
85
91
|
|
package/dist/dura.js
CHANGED
|
@@ -14019,6 +14019,62 @@ function generateDeploymentManifest(_projectDir, manifest) {
|
|
|
14019
14019
|
}
|
|
14020
14020
|
var init_manifest_generator = () => {};
|
|
14021
14021
|
|
|
14022
|
+
// src/lib/parity-check.ts
|
|
14023
|
+
import { builtinModules } from "node:module";
|
|
14024
|
+
import * as esbuild from "esbuild";
|
|
14025
|
+
function isNodeBuiltinSpecifier(specifier) {
|
|
14026
|
+
if (specifier.startsWith("node:"))
|
|
14027
|
+
return true;
|
|
14028
|
+
return BARE_BUILTIN_SET.has(specifier);
|
|
14029
|
+
}
|
|
14030
|
+
async function analyzeNodeBuiltinImports(entryPath) {
|
|
14031
|
+
const externalList = [
|
|
14032
|
+
"node:*",
|
|
14033
|
+
...builtinModules,
|
|
14034
|
+
...builtinModules.map((m) => `node:${m}`)
|
|
14035
|
+
];
|
|
14036
|
+
let result;
|
|
14037
|
+
try {
|
|
14038
|
+
result = await esbuild.build({
|
|
14039
|
+
entryPoints: [entryPath],
|
|
14040
|
+
bundle: true,
|
|
14041
|
+
metafile: true,
|
|
14042
|
+
write: false,
|
|
14043
|
+
platform: "neutral",
|
|
14044
|
+
format: "esm",
|
|
14045
|
+
logLevel: "silent",
|
|
14046
|
+
external: externalList
|
|
14047
|
+
});
|
|
14048
|
+
} catch {
|
|
14049
|
+
return [];
|
|
14050
|
+
}
|
|
14051
|
+
const seen = new Set;
|
|
14052
|
+
const hits = [];
|
|
14053
|
+
for (const [importerPath, info] of Object.entries(result.metafile.inputs)) {
|
|
14054
|
+
for (const imp of info.imports) {
|
|
14055
|
+
if (!imp.external)
|
|
14056
|
+
continue;
|
|
14057
|
+
if (!isNodeBuiltinSpecifier(imp.path))
|
|
14058
|
+
continue;
|
|
14059
|
+
const key = `${imp.path}\x00${importerPath}`;
|
|
14060
|
+
if (seen.has(key))
|
|
14061
|
+
continue;
|
|
14062
|
+
seen.add(key);
|
|
14063
|
+
hits.push({ specifier: imp.path, importer: importerPath });
|
|
14064
|
+
}
|
|
14065
|
+
}
|
|
14066
|
+
hits.sort((a, b2) => {
|
|
14067
|
+
if (a.specifier !== b2.specifier)
|
|
14068
|
+
return a.specifier < b2.specifier ? -1 : 1;
|
|
14069
|
+
return a.importer < b2.importer ? -1 : a.importer > b2.importer ? 1 : 0;
|
|
14070
|
+
});
|
|
14071
|
+
return hits;
|
|
14072
|
+
}
|
|
14073
|
+
var BARE_BUILTIN_SET;
|
|
14074
|
+
var init_parity_check = __esm(() => {
|
|
14075
|
+
BARE_BUILTIN_SET = new Set(builtinModules);
|
|
14076
|
+
});
|
|
14077
|
+
|
|
14022
14078
|
// src/lib/bundler.ts
|
|
14023
14079
|
import { join as join6, resolve as resolve2 } from "node:path";
|
|
14024
14080
|
import {
|
|
@@ -14031,7 +14087,7 @@ import {
|
|
|
14031
14087
|
} from "node:fs";
|
|
14032
14088
|
import { createGzip } from "node:zlib";
|
|
14033
14089
|
import { Readable } from "node:stream";
|
|
14034
|
-
import * as
|
|
14090
|
+
import * as esbuild2 from "esbuild";
|
|
14035
14091
|
async function createBundle(projectDir, options) {
|
|
14036
14092
|
const maxSize = options?.maxBundleSize ?? MAX_BUNDLE_SIZE_BYTES;
|
|
14037
14093
|
let duraManifest;
|
|
@@ -14048,6 +14104,7 @@ async function createBundle(projectDir, options) {
|
|
|
14048
14104
|
rmSync(buildDir, { recursive: true });
|
|
14049
14105
|
}
|
|
14050
14106
|
mkdirSync3(buildDir, { recursive: true });
|
|
14107
|
+
const parityWarnings = [];
|
|
14051
14108
|
for (const auto of duraManifest.automations) {
|
|
14052
14109
|
const entryPath = resolve2(projectDir, auto.entrypoint);
|
|
14053
14110
|
if (!existsSync5(entryPath)) {
|
|
@@ -14060,7 +14117,7 @@ async function createBundle(projectDir, options) {
|
|
|
14060
14117
|
const outFile = join6(buildDir, `${auto.name}.js`);
|
|
14061
14118
|
try {
|
|
14062
14119
|
const source = readFileSync5(entryPath, "utf-8");
|
|
14063
|
-
const xformed = await
|
|
14120
|
+
const xformed = await esbuild2.transform(source, {
|
|
14064
14121
|
loader: "ts",
|
|
14065
14122
|
target: "es2022",
|
|
14066
14123
|
sourcemap: false
|
|
@@ -14073,6 +14130,12 @@ async function createBundle(projectDir, options) {
|
|
|
14073
14130
|
error: `Failed to bundle ${auto.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
14074
14131
|
};
|
|
14075
14132
|
}
|
|
14133
|
+
try {
|
|
14134
|
+
const hits = await analyzeNodeBuiltinImports(entryPath);
|
|
14135
|
+
if (hits.length > 0) {
|
|
14136
|
+
parityWarnings.push({ automation: auto.name, hits });
|
|
14137
|
+
}
|
|
14138
|
+
} catch {}
|
|
14076
14139
|
}
|
|
14077
14140
|
const deployManifest = generateDeploymentManifest(projectDir, duraManifest);
|
|
14078
14141
|
writeFileSync5(join6(buildDir, "manifest.json"), JSON.stringify(deployManifest, null, 2));
|
|
@@ -14089,7 +14152,8 @@ async function createBundle(projectDir, options) {
|
|
|
14089
14152
|
success: true,
|
|
14090
14153
|
archiveBuffer,
|
|
14091
14154
|
manifest: deployManifest,
|
|
14092
|
-
sizeBytes: archiveBuffer.length
|
|
14155
|
+
sizeBytes: archiveBuffer.length,
|
|
14156
|
+
parityWarnings
|
|
14093
14157
|
};
|
|
14094
14158
|
}
|
|
14095
14159
|
async function createTarGz(dir) {
|
|
@@ -14181,6 +14245,7 @@ var init_bundler = __esm(() => {
|
|
|
14181
14245
|
init_src2();
|
|
14182
14246
|
init_manifest2();
|
|
14183
14247
|
init_manifest_generator();
|
|
14248
|
+
init_parity_check();
|
|
14184
14249
|
});
|
|
14185
14250
|
|
|
14186
14251
|
// src/commands/deploy.ts
|
|
@@ -14220,6 +14285,18 @@ function registerDeployCommand(program2) {
|
|
|
14220
14285
|
return;
|
|
14221
14286
|
}
|
|
14222
14287
|
output.info(`Bundle created: ${bundle.sizeBytes} bytes`);
|
|
14288
|
+
if (bundle.parityWarnings && bundle.parityWarnings.length > 0) {
|
|
14289
|
+
for (const w of bundle.parityWarnings) {
|
|
14290
|
+
const lines = [
|
|
14291
|
+
`"${w.automation}" imports Node built-ins that are not available in the dura.run runtime:`,
|
|
14292
|
+
...w.hits.map((h) => ` ${h.specifier.padEnd(22)} (imported by ${h.importer})`),
|
|
14293
|
+
`Your handler may fail when invoked in production.`,
|
|
14294
|
+
`See https://dura.run/docs/runtime for the available runtime surface.`
|
|
14295
|
+
];
|
|
14296
|
+
output.warn(lines.join(`
|
|
14297
|
+
`));
|
|
14298
|
+
}
|
|
14299
|
+
}
|
|
14223
14300
|
output.info("Deploying...");
|
|
14224
14301
|
try {
|
|
14225
14302
|
const result = await client.deployBundle(projectId, bundle.archiveBuffer, bundle.manifest);
|
|
@@ -14239,7 +14316,8 @@ function registerDeployCommand(program2) {
|
|
|
14239
14316
|
activatedAt: result.deployment.activatedAt,
|
|
14240
14317
|
bundleSize: bundle.sizeBytes,
|
|
14241
14318
|
automations: bundle.manifest.automations.length,
|
|
14242
|
-
urls: result.urls ?? []
|
|
14319
|
+
urls: result.urls ?? [],
|
|
14320
|
+
parityWarnings: bundle.parityWarnings ?? []
|
|
14243
14321
|
});
|
|
14244
14322
|
if (result.urls && result.urls.length > 0) {
|
|
14245
14323
|
output.info(`
|
|
@@ -14816,9 +14894,10 @@ function renderTrustRefusal() {
|
|
|
14816
14894
|
return [
|
|
14817
14895
|
`${bold}${yellow}dura dev refuses to start.${reset2}`,
|
|
14818
14896
|
"",
|
|
14819
|
-
"Your local machine has stored dura credentials, and `dura dev`
|
|
14820
|
-
"
|
|
14821
|
-
"
|
|
14897
|
+
"Your local machine has stored dura credentials, and `dura dev` loads",
|
|
14898
|
+
"your handler code with full filesystem access. Production isolation",
|
|
14899
|
+
"lives in @dura/executor; dura dev intentionally does not sandbox (see",
|
|
14900
|
+
"#147). You must explicitly accept this risk to start the dev server.",
|
|
14822
14901
|
"",
|
|
14823
14902
|
"To proceed, pick ONE of:",
|
|
14824
14903
|
"",
|
|
@@ -19513,7 +19592,7 @@ async function registerAllCommands(program2) {
|
|
|
19513
19592
|
registerOpenApiCommand2(program2);
|
|
19514
19593
|
return program2;
|
|
19515
19594
|
}
|
|
19516
|
-
var CLI_VERSION = "0.3.
|
|
19595
|
+
var CLI_VERSION = "0.3.3";
|
|
19517
19596
|
var init_src3 = __esm(() => {
|
|
19518
19597
|
init_esm();
|
|
19519
19598
|
if (import.meta.url === `file://${realpathSync(process.argv[1] ?? "").replace(/\\/g, "/")}`) {
|
|
@@ -19530,4 +19609,4 @@ export {
|
|
|
19530
19609
|
CLI_VERSION
|
|
19531
19610
|
};
|
|
19532
19611
|
|
|
19533
|
-
//# debugId=
|
|
19612
|
+
//# debugId=865D5FDBF88E91E164756E2164756E21
|
package/package.json
CHANGED
package/skills/dura-develop.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
> This skill teaches an AI agent how to scaffold projects, write automation handlers, use SDK globals, and run locally with `dura dev`.
|
|
4
4
|
|
|
5
|
+
## Runtime constraints
|
|
6
|
+
|
|
7
|
+
Handlers run in a V8 isolate on dura.run. **No Node built-ins, no filesystem, no child processes, no native addons.** If you reach for `node:fs`, `Buffer`, `child_process`, or `require('some-native-lib')`, the deploy will warn and the handler will fail at runtime.
|
|
8
|
+
|
|
9
|
+
**What IS available:**
|
|
10
|
+
|
|
11
|
+
- Standard JavaScript globals (`Date`, `Math`, `JSON`, `Promise`, `Uint8Array`, etc.)
|
|
12
|
+
- `crypto.randomUUID()` and `crypto.getRandomValues(typedArray)` (WebCrypto-spec random)
|
|
13
|
+
- `TextEncoder` and `TextDecoder` (UTF-8 only)
|
|
14
|
+
- The fetch API (via `dura.fetch`)
|
|
15
|
+
- The `dura.*` SDK surface: `fetch`, `log`, `env`, `kv`, `trigger`, `triggerAndWait`
|
|
16
|
+
|
|
17
|
+
**What is NOT available yet:**
|
|
18
|
+
|
|
19
|
+
- `crypto.subtle.*` (hashing, signing, key derivation). For these, call a trusted external service via `dura.fetch`.
|
|
20
|
+
|
|
21
|
+
**Use these replacements for common Node-isms:**
|
|
22
|
+
|
|
23
|
+
| Don't use | Use instead |
|
|
24
|
+
| --------------------- | ------------------------------------------------------------------------------------------ |
|
|
25
|
+
| `node:fs` | `dura.kv` or the artifact APIs |
|
|
26
|
+
| `node:crypto` for IDs | `crypto.randomUUID()` |
|
|
27
|
+
| `node:crypto` for random bytes | `crypto.getRandomValues(new Uint8Array(n))` |
|
|
28
|
+
| `node:crypto` for hashing/signing | Not yet supported — call an external service via `dura.fetch` |
|
|
29
|
+
| `Buffer` | `Uint8Array` with `TextEncoder` / `TextDecoder` |
|
|
30
|
+
| `node:child_process` | Not supported — there is no process boundary to use |
|
|
31
|
+
| `node:http` / `https` | `dura.fetch` |
|
|
32
|
+
| `process.env.X` | `dura.env.get("X")` |
|
|
33
|
+
|
|
34
|
+
If `dura deploy` warns about Node built-ins in your bundle, swap to the equivalent above before shipping.
|
|
35
|
+
|
|
5
36
|
## Scaffolding a New Project
|
|
6
37
|
|
|
7
38
|
### `dura new <name>` — Create a New Project
|