@intend-it/cli 1.3.2 → 1.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 +11 -0
- package/dist/index.js +58 -15
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -65,6 +65,17 @@ Creates:
|
|
|
65
65
|
- `intend.config.json` - Project configuration
|
|
66
66
|
- `src/intents/` - Directory for `.intent` files
|
|
67
67
|
- `out/` - Output directory for generated TypeScript
|
|
68
|
+
- `intend.lock` - Deterministic build lockfile (commit this!)
|
|
69
|
+
|
|
70
|
+
### 🔒 Build Lockfile (`intend.lock`)
|
|
71
|
+
|
|
72
|
+
To ensure that your builds are deterministic and fast, Intend uses a lockfile system.
|
|
73
|
+
|
|
74
|
+
When you run `intend build`, the CLI calculates a hash of your `.intent` source code and your configuration. If a match is found in `intend.lock`, the compiler uses the cached implementation instead of calling the AI provider.
|
|
75
|
+
|
|
76
|
+
- **Fast Rebuilds**: Near-instant builds for unchanged files.
|
|
77
|
+
- **Stable Production**: Your code won't change in CI/CD unless you explicitly change the intention.
|
|
78
|
+
- **Version Control**: You should always commit `intend.lock` to your repository.
|
|
68
79
|
|
|
69
80
|
### `intend build`
|
|
70
81
|
|
package/dist/index.js
CHANGED
|
@@ -254,14 +254,30 @@ async function initCommand(args) {
|
|
|
254
254
|
if (!existsSync2(examplePath)) {
|
|
255
255
|
const exampleContent = `import { User } from "../types";
|
|
256
256
|
|
|
257
|
+
export intent GenerateRandomId() -> string {
|
|
258
|
+
step "Generate a random numeric ID" => const id: number
|
|
259
|
+
return id
|
|
260
|
+
}
|
|
261
|
+
|
|
257
262
|
export intent CreateUser(name: string) -> User {
|
|
258
|
-
|
|
259
|
-
step "Create a User object with name, id, and current date" => const user
|
|
263
|
+
GenerateRandomId() => const id
|
|
264
|
+
step "Create a User object with name, id, and current date" => const user: User
|
|
265
|
+
|
|
260
266
|
return user
|
|
261
267
|
}
|
|
262
268
|
|
|
269
|
+
// Entry point
|
|
263
270
|
export entry intent Main() -> void {
|
|
264
|
-
step "
|
|
271
|
+
step "Using console, ask the user for their name" => const name
|
|
272
|
+
|
|
273
|
+
CreateUser(name) => const user
|
|
274
|
+
|
|
275
|
+
invariant "Throw an error if the user.name is 'sudo' or 'root'"
|
|
276
|
+
|
|
277
|
+
ensure user.name is not empty
|
|
278
|
+
ensure user.id is not empty
|
|
279
|
+
ensure user.dateCreated is not undefined
|
|
280
|
+
|
|
265
281
|
step "Log 'Created user:' and the user object to console"
|
|
266
282
|
}
|
|
267
283
|
`;
|
|
@@ -289,7 +305,7 @@ out/
|
|
|
289
305
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync2, copyFileSync } from "fs";
|
|
290
306
|
import { execSync } from "child_process";
|
|
291
307
|
import { join as join3, resolve as resolve3, dirname, basename } from "path";
|
|
292
|
-
import { AICodeGenerator, FileSystemCAS, computeHash, OllamaProvider } from "@intend-it/core";
|
|
308
|
+
import { AICodeGenerator, FileSystemCAS, computeHash, OllamaProvider, LockfileManager } from "@intend-it/core";
|
|
293
309
|
import { parseToAST } from "@intend-it/parser";
|
|
294
310
|
import { readdirSync as readdirSync2, statSync } from "fs";
|
|
295
311
|
|
|
@@ -562,9 +578,13 @@ ${pc3.red(err.message)}`);
|
|
|
562
578
|
}
|
|
563
579
|
const casDir = resolve3(process.cwd(), ".intend", "store");
|
|
564
580
|
const cas = new FileSystemCAS(casDir);
|
|
581
|
+
const lockfilePath = resolve3(process.cwd(), "intend.lock");
|
|
582
|
+
const lockfile = new LockfileManager(lockfilePath);
|
|
583
|
+
let lockfileUpdated = false;
|
|
565
584
|
let successCount = 0;
|
|
566
585
|
let failCount = parseErrors;
|
|
567
586
|
let cachedCount = 0;
|
|
587
|
+
let lockedCount = 0;
|
|
568
588
|
const generatedFiles = [];
|
|
569
589
|
if (!existsSync3(outDir)) {
|
|
570
590
|
mkdirSync2(outDir, { recursive: true });
|
|
@@ -587,19 +607,39 @@ ${pc3.red(err.message)}`);
|
|
|
587
607
|
sFile.start(`${pc3.dim(relativePath)}`);
|
|
588
608
|
const fileStart = Date.now();
|
|
589
609
|
try {
|
|
590
|
-
const { hash, ast } = fileData.get(file);
|
|
591
|
-
const
|
|
610
|
+
const { content: sourceContent, hash, ast } = fileData.get(file);
|
|
611
|
+
const configHash = computeHash("", config);
|
|
612
|
+
const sourceOnlyHash = computeHash(sourceContent, {});
|
|
613
|
+
const lockedCode = lockfile.getEntry(relativePath, sourceOnlyHash, configHash);
|
|
592
614
|
let finalCode;
|
|
593
|
-
if (!options.force &&
|
|
594
|
-
sFile.stop(`${pc3.dim(relativePath)} ${pc3.
|
|
595
|
-
finalCode =
|
|
615
|
+
if (!options.force && lockedCode) {
|
|
616
|
+
sFile.stop(`${pc3.dim(relativePath)} ${pc3.green("(locked)")}`);
|
|
617
|
+
finalCode = lockedCode;
|
|
618
|
+
lockedCount++;
|
|
596
619
|
cachedCount++;
|
|
597
620
|
} else {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
621
|
+
const cached = await cas.get(hash);
|
|
622
|
+
if (!options.force && cached) {
|
|
623
|
+
sFile.stop(`${pc3.dim(relativePath)} ${pc3.blue("(cached)")}`);
|
|
624
|
+
finalCode = cached.code;
|
|
625
|
+
cachedCount++;
|
|
626
|
+
lockfile.setEntry(relativePath, sourceOnlyHash, configHash, finalCode, {
|
|
627
|
+
model: modelName,
|
|
628
|
+
provider: providerName
|
|
629
|
+
});
|
|
630
|
+
lockfileUpdated = true;
|
|
631
|
+
} else {
|
|
632
|
+
const generated = await generator.generate(ast, file);
|
|
633
|
+
finalCode = generated.code;
|
|
634
|
+
await cas.put(hash, finalCode);
|
|
635
|
+
lockfile.setEntry(relativePath, sourceOnlyHash, configHash, finalCode, {
|
|
636
|
+
model: modelName,
|
|
637
|
+
provider: providerName
|
|
638
|
+
});
|
|
639
|
+
lockfileUpdated = true;
|
|
640
|
+
const fileDuration = Date.now() - fileStart;
|
|
641
|
+
sFile.stop(`${relativePath} ${duration(fileDuration)}`);
|
|
642
|
+
}
|
|
603
643
|
}
|
|
604
644
|
writeFileSync3(outFile, finalCode, "utf-8");
|
|
605
645
|
generatedFiles.push(outFile);
|
|
@@ -609,9 +649,12 @@ ${pc3.red(err.message)}`);
|
|
|
609
649
|
failCount++;
|
|
610
650
|
}
|
|
611
651
|
}
|
|
652
|
+
if (lockfileUpdated) {
|
|
653
|
+
lockfile.save();
|
|
654
|
+
}
|
|
612
655
|
const totalDuration = Date.now() - startTime;
|
|
613
656
|
if (!isWatch) {
|
|
614
|
-
p2.note(`${pc3.green(icons.success)} ${pc3.bold(successCount)} compiled ${cachedCount > 0 ? pc3.dim(`(${cachedCount} cached)`) : ""}
|
|
657
|
+
p2.note(`${pc3.green(icons.success)} ${pc3.bold(successCount)} compiled ${cachedCount > 0 ? pc3.dim(`(${lockedCount} locked, ${cachedCount - lockedCount} cached)`) : ""}
|
|
615
658
|
` + (failCount > 0 ? `${pc3.red(icons.error)} ${pc3.bold(failCount)} failed
|
|
616
659
|
` : "") + `${pc3.white(icons.arrow)} ${pc3.white(duration(totalDuration))}`, "Summary");
|
|
617
660
|
const entryFiles = files.filter((f) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intend-it/cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "CLI for the Intend programming language",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^0.11.0",
|
|
33
|
-
"@intend-it/core": "^4.0.
|
|
34
|
-
"@intend-it/parser": "^1.3.
|
|
33
|
+
"@intend-it/core": "^4.0.3",
|
|
34
|
+
"@intend-it/parser": "^1.3.3",
|
|
35
35
|
"ora": "^8.1.1",
|
|
36
36
|
"picocolors": "^1.1.1"
|
|
37
37
|
},
|