agency-lang 0.0.98 → 0.0.100

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 (44) hide show
  1. package/dist/lib/backends/typescriptBuilder.js +3 -2
  2. package/dist/lib/cli/commands.d.ts +2 -0
  3. package/dist/lib/cli/commands.js +16 -24
  4. package/dist/lib/cli/debug.js +4 -9
  5. package/dist/lib/cli/util.d.ts +5 -0
  6. package/dist/lib/cli/util.js +35 -24
  7. package/dist/lib/debugger/ui.js +1 -1
  8. package/dist/lib/debugger/uiState.d.ts +6 -0
  9. package/dist/lib/debugger/uiState.js +25 -7
  10. package/dist/lib/importPaths.d.ts +6 -8
  11. package/dist/lib/importPaths.js +14 -26
  12. package/dist/lib/importPaths.test.js +55 -33
  13. package/dist/lib/importStrategy.d.ts +37 -0
  14. package/dist/lib/importStrategy.js +64 -0
  15. package/dist/lib/version.d.ts +1 -1
  16. package/dist/lib/version.js +1 -1
  17. package/package.json +1 -1
  18. package/stdlib/agent.js +1 -1
  19. package/stdlib/array.js +1809 -1778
  20. package/stdlib/clipboard.js +230 -57
  21. package/stdlib/fs.js +1 -1
  22. package/stdlib/http.js +670 -0
  23. package/stdlib/index.agency +2 -3
  24. package/stdlib/index.js +1872 -1856
  25. package/stdlib/{_builtins.js → lib/builtins.js} +1 -1
  26. package/stdlib/lib/ui.js +1 -1
  27. package/stdlib/math.agency +3 -5
  28. package/stdlib/math.js +5 -9
  29. package/stdlib/object.js +1 -1
  30. package/stdlib/path.js +1 -1
  31. package/stdlib/shell.js +1 -1
  32. package/stdlib/speech.js +249 -62
  33. package/stdlib/strategy.js +1 -1
  34. package/stdlib/system.js +1 -1
  35. package/stdlib/ui.js +1604 -0
  36. package/stdlib/weather.js +587 -0
  37. package/stdlib/{retry.js → wikipedia.js} +208 -147
  38. package/stdlib/_clipboard.js +0 -29
  39. package/stdlib/_math.js +0 -9
  40. package/stdlib/_speech.js +0 -69
  41. package/stdlib/_system.js +0 -29
  42. package/stdlib/consensus.js +0 -359
  43. package/stdlib/firstValid.js +0 -384
  44. package/stdlib/sample.js +0 -350
@@ -1,3 +1,4 @@
1
+ import path from "path";
1
2
  import { formatTypeHint } from "../cli/util.js";
2
3
  import { BUILTIN_FUNCTIONS, BUILTIN_TOOLS, BUILTIN_VARIABLES, TYPES_THAT_DONT_TRIGGER_NEW_PART, } from "../config.js";
3
4
  import { expressionToString } from "../utils/node.js";
@@ -1041,7 +1042,7 @@ export class TypeScriptBuilder {
1041
1042
  return ts.runnerIfElse({ id, branches, elseBranch });
1042
1043
  }
1043
1044
  processImportStatement(node) {
1044
- const from = toCompiledImportPath(node.modulePath);
1045
+ const from = toCompiledImportPath(node.modulePath, path.resolve(this.moduleId));
1045
1046
  const imports = node.importedNames.map((nameType) => {
1046
1047
  switch (nameType.type) {
1047
1048
  case "namedImport":
@@ -1092,7 +1093,7 @@ export class TypeScriptBuilder {
1092
1093
  return ts.importDecl({
1093
1094
  importKind: "named",
1094
1095
  names: importNames,
1095
- from: toCompiledImportPath(node.agencyFile),
1096
+ from: toCompiledImportPath(node.agencyFile, path.resolve(this.moduleId)),
1096
1097
  });
1097
1098
  }
1098
1099
  // ------- TsRaw wrapper methods (template-heavy) -------
@@ -1,6 +1,7 @@
1
1
  import { AgencyConfig } from "../config.js";
2
2
  import { AgencyProgram } from "../index.js";
3
3
  import { type SymbolTable } from "../symbolTable.js";
4
+ import { type ImportStrategy } from "../importStrategy.js";
4
5
  export declare function loadConfig(configPath?: string, verbose?: boolean): AgencyConfig;
5
6
  export declare function readStdin(): Promise<string>;
6
7
  export declare function parse(contents: string, config: AgencyConfig, applyTemplate?: boolean): AgencyProgram;
@@ -9,6 +10,7 @@ export declare function resetCompilationCache(): void;
9
10
  export declare function compile(config: AgencyConfig, inputFile: string, _outputFile?: string, options?: {
10
11
  ts?: boolean;
11
12
  symbolTable?: SymbolTable;
13
+ importStrategy?: ImportStrategy;
12
14
  }): string | null;
13
15
  export declare function run(config: AgencyConfig, inputFile: string, outputFile?: string, resumeFile?: string): void;
14
16
  export declare function format(contents: string, config: AgencyConfig): Promise<string>;
@@ -8,7 +8,8 @@ import { spawn } from "child_process";
8
8
  import { transformSync } from "esbuild";
9
9
  import * as fs from "fs";
10
10
  import * as path from "path";
11
- import { getStdlibDir, isPkgImport, isStdlibImport, resolveAgencyImportPath, resolveFlexibleExtension, } from "../importPaths.js";
11
+ import { getStdlibDir, isPkgImport, isStdlibImport, resolveAgencyImportPath, } from "../importPaths.js";
12
+ import { CompileStrategy, RunStrategy } from "../importStrategy.js";
12
13
  import { parseAgency } from "../parser.js";
13
14
  import { findRecursively, getImports } from "./util.js";
14
15
  // Load configuration from agency.json
@@ -126,8 +127,11 @@ export function compile(config, inputFile, _outputFile, options) {
126
127
  }
127
128
  const imports = getImports(resolvedProgram);
128
129
  for (const importPath of imports) {
130
+ // stdlib and pkg imports are pre-compiled; don't recompile them
131
+ if (isStdlibImport(importPath) || isPkgImport(importPath))
132
+ continue;
129
133
  const absPath = resolveAgencyImportPath(importPath, absoluteInputFile);
130
- if (config.restrictImports && !isStdlibImport(importPath) && !isPkgImport(importPath)) {
134
+ if (config.restrictImports) {
131
135
  const projectRoot = process.cwd();
132
136
  if (!absPath.startsWith(projectRoot + path.sep) &&
133
137
  absPath !== projectRoot) {
@@ -136,33 +140,21 @@ export function compile(config, inputFile, _outputFile, options) {
136
140
  }
137
141
  compile(config, absPath, undefined, { ...options, symbolTable });
138
142
  }
139
- // Update import paths in the AST
143
+ // Rewrite import paths in the AST using the import strategy
144
+ const strategy = options?.importStrategy ?? new CompileStrategy({ targetExt: ext });
145
+ const nonAgencyImports = [];
140
146
  resolvedProgram.nodes.forEach((node) => {
141
147
  if (node.type !== "importStatement")
142
148
  return;
143
149
  if (isStdlibImport(node.modulePath) || isPkgImport(node.modulePath))
144
150
  return;
145
- if (node.modulePath.endsWith(".agency")) {
146
- node.modulePath = node.modulePath.replace(".agency", ext);
147
- return;
148
- }
149
- // For .js/.ts imports, resolve flexibly: if the specified file doesn't
150
- // exist, try the other extension. This lets users write .js imports that
151
- // work in dist/ while the debugger finds the .ts source (and vice versa).
152
- if (node.modulePath.endsWith(".js") || node.modulePath.endsWith(".ts")) {
153
- const resolved = resolveFlexibleExtension(node.modulePath, absoluteInputFile);
154
- if (resolved === null) {
155
- const altExt = node.modulePath.endsWith(".js") ? ".ts" : ".js";
156
- const altPath = node.modulePath.replace(/\.(js|ts)$/, altExt);
157
- console.error(`Error: Cannot resolve import '${node.modulePath}' from '${inputFile}'.\n` +
158
- `Tried: ${node.modulePath}, ${altPath} — neither file exists.`);
159
- process.exit(1);
160
- }
161
- // Rewrite the import to use whatever extension actually exists on disk
162
- const resolvedExt = path.extname(resolved);
163
- node.modulePath = node.modulePath.replace(/\.(js|ts)$/, resolvedExt);
151
+ node.modulePath = strategy.rewriteImport(node.modulePath, absoluteInputFile);
152
+ // Collect non-Agency imports for dependency preparation
153
+ if (!node.modulePath.endsWith(".agency")) {
154
+ nonAgencyImports.push(node.modulePath);
164
155
  }
165
156
  });
157
+ strategy.prepareDependencies(nonAgencyImports, absoluteInputFile);
166
158
  const moduleId = path.relative(process.cwd(), absoluteInputFile);
167
159
  const generatedCode = generateTypeScript(resolvedProgram, config, info, moduleId);
168
160
  if (options?.ts) {
@@ -182,8 +174,8 @@ export function compile(config, inputFile, _outputFile, options) {
182
174
  return outputFile;
183
175
  }
184
176
  export function run(config, inputFile, outputFile, resumeFile) {
185
- // Compile the file
186
- const output = compile(config, inputFile, outputFile);
177
+ // Compile the file with RunStrategy so dependencies are prepared for execution
178
+ const output = compile(config, inputFile, outputFile, { importStrategy: new RunStrategy() });
187
179
  if (output === null) {
188
180
  console.error("Error: No output file generated.");
189
181
  process.exit(1);
@@ -1,5 +1,6 @@
1
1
  import { compile } from "./commands.js";
2
- import { pickANode } from "./util.js";
2
+ import { RunStrategy } from "../importStrategy.js";
3
+ import { pickANode, resolveCompiledFile } from "./util.js";
3
4
  import { parseAgency } from "../parser.js";
4
5
  import { getNodesOfType } from "../utils/node.js";
5
6
  import { DebuggerDriver } from "../debugger/driver.js";
@@ -82,13 +83,7 @@ export async function debug(config, _inputFile, options = {}) {
82
83
  let absOutput;
83
84
  if (distDir) {
84
85
  // distDir mode: import pre-compiled JS from the dist directory
85
- const basename = path.basename(inputFile, ".agency") + ".js";
86
- const compiledPath = path.resolve(distDir, basename);
87
- if (!fs.existsSync(compiledPath)) {
88
- console.error(`Error: Compiled file not found: ${compiledPath}\n` +
89
- `Make sure you have compiled your Agency files and that distDir is correct.`);
90
- process.exit(1);
91
- }
86
+ const compiledPath = resolveCompiledFile(distDir, inputFile);
92
87
  // Warn if source is newer than compiled output
93
88
  const sourceMtime = fs.statSync(inputFile).mtimeMs;
94
89
  const compiledMtime = fs.statSync(compiledPath).mtimeMs;
@@ -101,7 +96,7 @@ export async function debug(config, _inputFile, options = {}) {
101
96
  else {
102
97
  // Normal mode: compile the .agency file to .js on the fly
103
98
  const debugConfig = { ...config, debugger: true };
104
- const outputFile = compile(debugConfig, inputFile);
99
+ const outputFile = compile(debugConfig, inputFile, undefined, { importStrategy: new RunStrategy() });
105
100
  if (outputFile === null) {
106
101
  console.error("Error: No output file generated.");
107
102
  process.exit(1);
@@ -21,6 +21,11 @@ export type InterruptHandler = {
21
21
  resolvedValue?: any;
22
22
  expectedMessage?: string;
23
23
  };
24
+ /**
25
+ * Resolve the compiled .js file for an .agency file from a distDir.
26
+ * Throws if the compiled file doesn't exist.
27
+ */
28
+ export declare function resolveCompiledFile(distDir: string, agencyFile: string): string;
24
29
  type ExecuteNodeArgs = {
25
30
  config: AgencyConfig;
26
31
  agencyFile: string;
@@ -11,6 +11,7 @@ import { isAgencyImport, resolveAgencyImportPath, getStdlibDir } from "../import
11
11
  import renderEvaluate from "../templates/cli/evaluate.js";
12
12
  import renderJudgeEvaluate from "../templates/cli/judgeEvaluate.js";
13
13
  import { compile } from "./commands.js";
14
+ import { RunStrategy } from "../importStrategy.js";
14
15
  import { parseAgency } from "../parser.js";
15
16
  export function parseTarget(target) {
16
17
  const colonIndex = target.lastIndexOf(":");
@@ -103,28 +104,39 @@ export async function promptForArgs(selectedNode) {
103
104
  }
104
105
  return { hasArgs, argsString };
105
106
  }
107
+ /**
108
+ * Resolve the compiled .js file for an .agency file from a distDir.
109
+ * Throws if the compiled file doesn't exist.
110
+ */
111
+ export function resolveCompiledFile(distDir, agencyFile) {
112
+ const basename = path.basename(agencyFile, ".agency") + ".js";
113
+ const compiledPath = path.resolve(distDir, basename);
114
+ if (!fs.existsSync(compiledPath)) {
115
+ throw new Error(`Compiled file not found: ${compiledPath}\n` +
116
+ `Make sure you have compiled your Agency files and that distDir is correct.`);
117
+ }
118
+ return compiledPath;
119
+ }
106
120
  export async function executeNodeAsync({ config, agencyFile, nodeName, hasArgs, argsString, interruptHandlers, }) {
107
121
  const distDir = config.distDir;
108
- let compiledFilename;
122
+ let compiledPath;
109
123
  if (distDir) {
110
- // distDir mode: skip compilation, import pre-compiled JS from distDir
111
- const basename = path.basename(agencyFile, ".agency") + ".js";
112
- const compiledPath = path.resolve(distDir, basename);
113
- if (!fs.existsSync(compiledPath)) {
114
- throw new Error(`Compiled file not found: ${compiledPath}\n` +
115
- `Make sure you have compiled your Agency files and that distDir is correct.`);
116
- }
117
- compiledFilename = compiledPath;
124
+ compiledPath = resolveCompiledFile(distDir, agencyFile);
118
125
  }
119
126
  else {
120
- compile(config, agencyFile);
121
- compiledFilename = path.basename(agencyFile).replace(".agency", ".js");
127
+ compiledPath = compile(config, agencyFile, undefined, { importStrategy: new RunStrategy() });
122
128
  }
123
129
  const baseName = agencyFile.replace(".agency", "");
124
130
  const evaluateFile = `${baseName}.evaluate.js`;
125
131
  const resultsFile = `${baseName}.evaluate.json`;
132
+ // The template imports via "./${filename}", so compute a relative path
133
+ // from the evaluate script's directory to the compiled module.
134
+ let importSpecifier = path.relative(path.dirname(evaluateFile), compiledPath).replace(/\\/g, "/");
135
+ if (!importSpecifier.startsWith(".")) {
136
+ importSpecifier = `./${importSpecifier}`;
137
+ }
126
138
  const evaluateScript = renderEvaluate({
127
- filename: compiledFilename,
139
+ filename: importSpecifier,
128
140
  nodeName,
129
141
  hasArgs,
130
142
  args: argsString,
@@ -153,22 +165,22 @@ export async function executeNodeAsync({ config, agencyFile, nodeName, hasArgs,
153
165
  }
154
166
  export function executeNode(args) {
155
167
  const distDir = args.config.distDir;
156
- let compiledFilename;
168
+ let compiledPath;
157
169
  if (distDir) {
158
- const basename = path.basename(args.agencyFile, ".agency") + ".js";
159
- const compiledPath = path.resolve(distDir, basename);
160
- if (!fs.existsSync(compiledPath)) {
161
- throw new Error(`Compiled file not found: ${compiledPath}\n` +
162
- `Make sure you have compiled your Agency files and that distDir is correct.`);
163
- }
164
- compiledFilename = compiledPath;
170
+ compiledPath = resolveCompiledFile(distDir, args.agencyFile);
165
171
  }
166
172
  else {
167
- compile(args.config, args.agencyFile);
168
- compiledFilename = args.agencyFile.replace(".agency", ".js");
173
+ compiledPath = compile(args.config, args.agencyFile, undefined, { importStrategy: new RunStrategy() });
174
+ }
175
+ const evaluateFile = "__evaluate.js";
176
+ // The template imports via "./${filename}", so compute a relative path
177
+ // from the evaluate script's directory to the compiled module.
178
+ let importSpecifier = path.relative(path.dirname(evaluateFile), compiledPath).replace(/\\/g, "/");
179
+ if (!importSpecifier.startsWith(".")) {
180
+ importSpecifier = `./${importSpecifier}`;
169
181
  }
170
182
  const evaluateScript = renderEvaluate({
171
- filename: compiledFilename,
183
+ filename: importSpecifier,
172
184
  nodeName: args.nodeName,
173
185
  hasArgs: args.hasArgs,
174
186
  args: args.argsString,
@@ -178,7 +190,6 @@ export function executeNode(args) {
178
190
  : undefined,
179
191
  resultsFilename: "__evaluate.json",
180
192
  });
181
- const evaluateFile = "__evaluate.js";
182
193
  fs.writeFileSync(evaluateFile, evaluateScript);
183
194
  execFileSync("node", [evaluateFile], { stdio: "inherit" });
184
195
  const results = readFileSync("__evaluate.json", "utf-8");
@@ -395,7 +395,7 @@ export class DebuggerUI {
395
395
  renderSourcePane() {
396
396
  const moduleId = this.state.getModuleId();
397
397
  const currentLine = this.state.getCurrentLine();
398
- const filePath = moduleId;
398
+ const filePath = this.state.resolveModulePath(moduleId, [".agency"]) ?? moduleId;
399
399
  const fileContent = readSourceFile(filePath);
400
400
  const highlighted = syntaxHighlight(fileContent, "ts");
401
401
  const lines = highlighted.split("\n");
@@ -19,6 +19,12 @@ export declare class UIState {
19
19
  private sourceMapCache;
20
20
  constructor();
21
21
  static fromCheckpoint(checkpoint: Checkpoint | undefined): UIState;
22
+ /**
23
+ * Resolve a moduleId (e.g., "stdlib/index.agency") to an actual file path,
24
+ * trying the given extensions. Handles stdlib modules that live inside
25
+ * node_modules/agency-lang/stdlib/ when running from an external project.
26
+ */
27
+ resolveModulePath(moduleId: string, extensions: string[]): string | null;
22
28
  private agencyToJsFile;
23
29
  setCheckpoint(checkpoint: Checkpoint | undefined): Promise<void>;
24
30
  setWithOverrides(obj: Record<string, unknown>, array: ValWithOverride[]): void;
@@ -1,5 +1,6 @@
1
1
  import { Checkpoint } from "../runtime/state/checkpointStore.js";
2
2
  import { GlobalStore } from "../runtime/state/globalStore.js";
3
+ import { getStdlibDir } from "../importPaths.js";
3
4
  import fs from "fs";
4
5
  import path from "path";
5
6
  export class UIState {
@@ -30,17 +31,34 @@ export class UIState {
30
31
  uiState.setCheckpoint(checkpoint);
31
32
  return uiState;
32
33
  }
33
- agencyToJsFile(agencyFile) {
34
- const tsFile = agencyFile.replace(/\.agency$/, ".ts");
35
- if (fs.existsSync(tsFile)) {
36
- return tsFile;
34
+ /**
35
+ * Resolve a moduleId (e.g., "stdlib/index.agency") to an actual file path,
36
+ * trying the given extensions. Handles stdlib modules that live inside
37
+ * node_modules/agency-lang/stdlib/ when running from an external project.
38
+ */
39
+ resolveModulePath(moduleId, extensions) {
40
+ // Try the path as-is first (works when running from the agency-lang repo)
41
+ for (const ext of extensions) {
42
+ const candidate = moduleId.replace(/\.agency$/, ext);
43
+ if (fs.existsSync(candidate))
44
+ return candidate;
37
45
  }
38
- const jsFile = agencyFile.replace(/\.agency$/, ".js");
39
- if (fs.existsSync(jsFile)) {
40
- return jsFile;
46
+ // If the moduleId refers to a stdlib file, resolve against the actual
47
+ // stdlib directory (handles npm-installed packages where stdlib lives
48
+ // in node_modules/agency-lang/stdlib/)
49
+ if (moduleId.includes("stdlib")) {
50
+ const basename = path.basename(moduleId);
51
+ for (const ext of extensions) {
52
+ const candidate = path.join(getStdlibDir(), basename).replace(/\.agency$/, ext);
53
+ if (fs.existsSync(candidate))
54
+ return candidate;
55
+ }
41
56
  }
42
57
  return null;
43
58
  }
59
+ agencyToJsFile(agencyFile) {
60
+ return this.resolveModulePath(agencyFile, [".js", ".ts"]);
61
+ }
44
62
  async setCheckpoint(checkpoint) {
45
63
  const cp = Checkpoint.fromJSON(checkpoint);
46
64
  if (!cp) {
@@ -64,17 +64,15 @@ export declare function resolvePkgAgencyPath(importPath: string, fromFile: strin
64
64
  * - "./foo.js" -> resolved relative to the importing file (non-agency, kept as-is)
65
65
  */
66
66
  export declare function resolveAgencyImportPath(importPath: string, fromFile: string): string;
67
- /**
68
- * Resolve a .js or .ts import path, trying the other extension if the
69
- * specified file doesn't exist. Returns the resolved absolute path, or
70
- * null if neither extension exists on disk.
71
- */
72
- export declare function resolveFlexibleExtension(importPath: string, fromFile: string): string | null;
73
67
  /**
74
68
  * Convert an Agency import path to the path that should appear in generated
75
69
  * TypeScript import statements.
76
70
  *
77
- * - "std::foo" -> absolute path to <stdlib-dir>/foo.js
71
+ * - "std::foo" -> relative path to <stdlib-dir>/foo.js from the source file
78
72
  * - "./foo.agency" -> "./foo.js" (relative, just extension swap)
73
+ *
74
+ * @param fromFile - Absolute path of the source file containing the import.
75
+ * Used to compute relative paths for stdlib imports. If not provided,
76
+ * falls back to absolute paths.
79
77
  */
80
- export declare function toCompiledImportPath(importPath: string): string;
78
+ export declare function toCompiledImportPath(importPath: string, fromFile?: string): string;
@@ -216,39 +216,27 @@ export function resolveAgencyImportPath(importPath, fromFile) {
216
216
  // Relative or other imports: resolve against the importing file's directory
217
217
  return path.resolve(path.dirname(fromFile), importPath);
218
218
  }
219
- const FLEXIBLE_EXTENSIONS = {
220
- ".js": ".ts",
221
- ".ts": ".js",
222
- };
223
- /**
224
- * Resolve a .js or .ts import path, trying the other extension if the
225
- * specified file doesn't exist. Returns the resolved absolute path, or
226
- * null if neither extension exists on disk.
227
- */
228
- export function resolveFlexibleExtension(importPath, fromFile) {
229
- const ext = path.extname(importPath);
230
- const altExt = FLEXIBLE_EXTENSIONS[ext];
231
- if (!altExt)
232
- return null; // not a .js/.ts import
233
- const dir = path.dirname(fromFile);
234
- const resolved = path.resolve(dir, importPath);
235
- if (fs.existsSync(resolved))
236
- return resolved;
237
- const altPath = resolved.slice(0, -ext.length) + altExt;
238
- if (fs.existsSync(altPath))
239
- return altPath;
240
- return null;
241
- }
242
219
  /**
243
220
  * Convert an Agency import path to the path that should appear in generated
244
221
  * TypeScript import statements.
245
222
  *
246
- * - "std::foo" -> absolute path to <stdlib-dir>/foo.js
223
+ * - "std::foo" -> relative path to <stdlib-dir>/foo.js from the source file
247
224
  * - "./foo.agency" -> "./foo.js" (relative, just extension swap)
225
+ *
226
+ * @param fromFile - Absolute path of the source file containing the import.
227
+ * Used to compute relative paths for stdlib imports. If not provided,
228
+ * falls back to absolute paths.
248
229
  */
249
- export function toCompiledImportPath(importPath) {
230
+ export function toCompiledImportPath(importPath, fromFile) {
250
231
  if (isStdlibImport(importPath)) {
251
- return path.join(STDLIB_DIR, normalizeStdlibPath(importPath) + ".js");
232
+ const absTarget = path.join(STDLIB_DIR, normalizeStdlibPath(importPath) + ".js");
233
+ if (fromFile) {
234
+ let rel = path.relative(path.dirname(fromFile), absTarget).replace(/\\/g, "/");
235
+ if (!rel.startsWith("."))
236
+ rel = "./" + rel;
237
+ return rel;
238
+ }
239
+ return absTarget;
252
240
  }
253
241
  if (isPkgImport(importPath)) {
254
242
  // Emit bare specifier — Node resolves it at runtime via node_modules
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { findPackageRoot, resolveAgencyImportPath, resolveFlexibleExtension, getStdlibDir, toCompiledImportPath, isPkgImport, isAgencyImport, parsePkgImport, resolvePkgAgencyPath, } from "./importPaths.js";
2
+ import { findPackageRoot, resolveAgencyImportPath, getStdlibDir, toCompiledImportPath, isPkgImport, isAgencyImport, parsePkgImport, resolvePkgAgencyPath, } from "./importPaths.js";
3
+ import { CompileStrategy, RunStrategy } from "./importStrategy.js";
3
4
  import { buildSymbolTable } from "./symbolTable.js";
4
5
  import * as fs from "fs";
5
6
  import * as os from "os";
@@ -235,52 +236,73 @@ describe("buildSymbolTable with pkg:: imports", () => {
235
236
  }
236
237
  });
237
238
  });
238
- describe("resolveFlexibleExtension", () => {
239
+ describe("CompileStrategy", () => {
240
+ const jsStrategy = new CompileStrategy({ targetExt: ".js" });
241
+ const tsStrategy = new CompileStrategy({ targetExt: ".ts" });
242
+ it("should rewrite .agency to .js", () => {
243
+ expect(jsStrategy.rewriteImport("./foo.agency", "/src/main.agency")).toBe("./foo.js");
244
+ });
245
+ it("should rewrite .agency to .ts with --ts", () => {
246
+ expect(tsStrategy.rewriteImport("./foo.agency", "/src/main.agency")).toBe("./foo.ts");
247
+ });
248
+ it("should leave .js imports untouched", () => {
249
+ expect(jsStrategy.rewriteImport("./tools.js", "/src/main.agency")).toBe("./tools.js");
250
+ });
251
+ it("should leave .ts imports untouched", () => {
252
+ expect(jsStrategy.rewriteImport("./tools.ts", "/src/main.agency")).toBe("./tools.ts");
253
+ });
254
+ it("should leave bare specifiers untouched", () => {
255
+ expect(jsStrategy.rewriteImport("nanoid", "/src/main.agency")).toBe("nanoid");
256
+ });
257
+ });
258
+ describe("RunStrategy", () => {
259
+ const strategy = new RunStrategy();
260
+ it("should rewrite .agency to .js", () => {
261
+ expect(strategy.rewriteImport("./foo.agency", "/src/main.agency")).toBe("./foo.js");
262
+ });
263
+ it("should rewrite .ts to .js", () => {
264
+ expect(strategy.rewriteImport("./tools.ts", "/src/main.agency")).toBe("./tools.js");
265
+ });
266
+ it("should leave .js imports as-is", () => {
267
+ expect(strategy.rewriteImport("./tools.js", "/src/main.agency")).toBe("./tools.js");
268
+ });
269
+ it("should leave bare specifiers untouched", () => {
270
+ expect(strategy.rewriteImport("nanoid", "/src/main.agency")).toBe("nanoid");
271
+ });
272
+ });
273
+ describe("RunStrategy.prepareDependencies", () => {
239
274
  let tmpDir;
275
+ const strategy = new RunStrategy();
240
276
  beforeEach(() => {
241
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agency-flex-ext-"));
277
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "agency-run-strategy-"));
242
278
  });
243
279
  afterEach(() => {
244
280
  fs.rmSync(tmpDir, { recursive: true, force: true });
245
281
  });
246
- it("should return the resolved path when the file exists as-is", () => {
282
+ it("should compile .ts to .js when .js doesn't exist", () => {
247
283
  const tsFile = path.join(tmpDir, "bar.ts");
248
- fs.writeFileSync(tsFile, "export const x = 1;");
249
- const fromFile = path.join(tmpDir, "main.agency");
250
- const result = resolveFlexibleExtension("./bar.ts", fromFile);
251
- expect(result).toBe(tsFile);
252
- });
253
- it("should fall back from .js to .ts when .js doesn't exist", () => {
254
- const tsFile = path.join(tmpDir, "bar.ts");
255
- fs.writeFileSync(tsFile, "export const x = 1;");
256
- const fromFile = path.join(tmpDir, "main.agency");
257
- const result = resolveFlexibleExtension("./bar.js", fromFile);
258
- expect(result).toBe(tsFile);
259
- });
260
- it("should fall back from .ts to .js when .ts doesn't exist", () => {
261
284
  const jsFile = path.join(tmpDir, "bar.js");
262
- fs.writeFileSync(jsFile, "export const x = 1;");
285
+ fs.writeFileSync(tsFile, "export const x: number = 1;");
263
286
  const fromFile = path.join(tmpDir, "main.agency");
264
- const result = resolveFlexibleExtension("./bar.ts", fromFile);
265
- expect(result).toBe(jsFile);
287
+ strategy.prepareDependencies(["./bar.js"], fromFile);
288
+ expect(fs.existsSync(jsFile)).toBe(true);
289
+ const content = fs.readFileSync(jsFile, "utf-8");
290
+ expect(content).toContain("const x = 1");
266
291
  });
267
- it("should return null when neither .js nor .ts exists", () => {
292
+ it("should not overwrite existing .js files", () => {
293
+ const jsFile = path.join(tmpDir, "bar.js");
294
+ fs.writeFileSync(jsFile, "// original");
268
295
  const fromFile = path.join(tmpDir, "main.agency");
269
- const result = resolveFlexibleExtension("./bar.js", fromFile);
270
- expect(result).toBeNull();
296
+ strategy.prepareDependencies(["./bar.js"], fromFile);
297
+ expect(fs.readFileSync(jsFile, "utf-8")).toBe("// original");
271
298
  });
272
- it("should return null for non-.js/.ts extensions", () => {
299
+ it("should throw when neither .js nor .ts exists", () => {
273
300
  const fromFile = path.join(tmpDir, "main.agency");
274
- const result = resolveFlexibleExtension("./bar.css", fromFile);
275
- expect(result).toBeNull();
301
+ expect(() => strategy.prepareDependencies(["./bar.js"], fromFile)).toThrow(/Cannot resolve import/);
276
302
  });
277
- it("should prefer the exact extension when both files exist", () => {
278
- const jsFile = path.join(tmpDir, "bar.js");
279
- const tsFile = path.join(tmpDir, "bar.ts");
280
- fs.writeFileSync(jsFile, "export const x = 1;");
281
- fs.writeFileSync(tsFile, "export const x = 1;");
303
+ it("should skip bare specifiers", () => {
282
304
  const fromFile = path.join(tmpDir, "main.agency");
283
- expect(resolveFlexibleExtension("./bar.js", fromFile)).toBe(jsFile);
284
- expect(resolveFlexibleExtension("./bar.ts", fromFile)).toBe(tsFile);
305
+ // Should not throw — bare specifiers are skipped
306
+ strategy.prepareDependencies(["nanoid"], fromFile);
285
307
  });
286
308
  });
@@ -0,0 +1,37 @@
1
+ export type CompileOptions = {
2
+ /** Extension for .agency rewrites: ".js" or ".ts" */
3
+ targetExt: ".js" | ".ts";
4
+ };
5
+ export interface ImportStrategy {
6
+ /**
7
+ * Rewrite an import path for the output.
8
+ * Handles .agency, .js, and .ts imports.
9
+ */
10
+ rewriteImport(modulePath: string, sourceFile: string): string;
11
+ /**
12
+ * Ensure all non-Agency dependencies are available for execution.
13
+ * Called after compilation, before the output is executed.
14
+ * Errors if a dependency can't be resolved.
15
+ */
16
+ prepareDependencies(imports: string[], sourceFile: string): void;
17
+ }
18
+ /**
19
+ * Strategy for `agency compile`: produces output for a downstream build pipeline.
20
+ * Leaves .js/.ts imports untouched. Only rewrites .agency imports.
21
+ */
22
+ export declare class CompileStrategy implements ImportStrategy {
23
+ protected options: CompileOptions;
24
+ constructor(options: CompileOptions);
25
+ rewriteImport(modulePath: string, _sourceFile: string): string;
26
+ prepareDependencies(_imports: string[], _sourceFile: string): void;
27
+ }
28
+ /**
29
+ * Strategy for `agency run` / `agency debug` / `agency test`: compiles and
30
+ * immediately executes. All imports must resolve to .js files that exist on disk.
31
+ * Compiles .ts dependencies to .js via esbuild when needed.
32
+ */
33
+ export declare class RunStrategy extends CompileStrategy {
34
+ constructor();
35
+ rewriteImport(modulePath: string, sourceFile: string): string;
36
+ prepareDependencies(imports: string[], sourceFile: string): void;
37
+ }
@@ -0,0 +1,64 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { transformSync } from "esbuild";
4
+ /**
5
+ * Strategy for `agency compile`: produces output for a downstream build pipeline.
6
+ * Leaves .js/.ts imports untouched. Only rewrites .agency imports.
7
+ */
8
+ export class CompileStrategy {
9
+ options;
10
+ constructor(options) {
11
+ this.options = options;
12
+ }
13
+ rewriteImport(modulePath, _sourceFile) {
14
+ if (modulePath.endsWith(".agency")) {
15
+ return modulePath.replace(/\.agency$/, this.options.targetExt);
16
+ }
17
+ return modulePath;
18
+ }
19
+ prepareDependencies(_imports, _sourceFile) {
20
+ // No-op — user's build pipeline handles dependencies
21
+ }
22
+ }
23
+ /**
24
+ * Strategy for `agency run` / `agency debug` / `agency test`: compiles and
25
+ * immediately executes. All imports must resolve to .js files that exist on disk.
26
+ * Compiles .ts dependencies to .js via esbuild when needed.
27
+ */
28
+ export class RunStrategy extends CompileStrategy {
29
+ constructor() {
30
+ super({ targetExt: ".js" });
31
+ }
32
+ rewriteImport(modulePath, sourceFile) {
33
+ if (modulePath.endsWith(".agency")) {
34
+ return super.rewriteImport(modulePath, sourceFile);
35
+ }
36
+ // Always produce .js — Node needs .js at runtime
37
+ return modulePath.replace(/\.ts$/, ".js");
38
+ }
39
+ prepareDependencies(imports, sourceFile) {
40
+ for (const imp of imports) {
41
+ if (!imp.startsWith("./") && !imp.startsWith("../"))
42
+ continue;
43
+ if (!imp.endsWith(".js"))
44
+ continue;
45
+ const resolved = path.resolve(path.dirname(sourceFile), imp);
46
+ if (fs.existsSync(resolved))
47
+ continue;
48
+ const tsPath = resolved.replace(/\.js$/, ".ts");
49
+ if (fs.existsSync(tsPath)) {
50
+ const tsCode = fs.readFileSync(tsPath, "utf-8");
51
+ const result = transformSync(tsCode, {
52
+ loader: "ts",
53
+ format: "esm",
54
+ supported: { "top-level-await": true },
55
+ });
56
+ fs.writeFileSync(resolved, result.code);
57
+ }
58
+ else {
59
+ throw new Error(`Cannot resolve import '${imp}' from '${sourceFile}'.\n` +
60
+ `Tried: ${resolved}, ${tsPath} — neither file exists.`);
61
+ }
62
+ }
63
+ }
64
+ }
@@ -1 +1 @@
1
- export declare const VERSION = "0.0.98";
1
+ export declare const VERSION = "0.0.100";
@@ -1 +1 @@
1
- export const VERSION = "0.0.98";
1
+ export const VERSION = "0.0.100";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.98",
3
+ "version": "0.0.100",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {