nola-lang 0.1.7 → 0.1.8

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 (68) hide show
  1. package/dist/build.d.ts +2 -0
  2. package/dist/build.js +77 -81
  3. package/dist/check.d.ts +1 -0
  4. package/dist/check.js +52 -51
  5. package/dist/commands.d.ts +1 -0
  6. package/dist/commands.js +5 -2
  7. package/dist/declarations.d.ts +7 -4
  8. package/dist/declarations.js +37 -25
  9. package/dist/derive.d.ts +21 -0
  10. package/dist/derive.js +27 -0
  11. package/dist/skill.d.ts +1 -1
  12. package/dist/skill.js +2 -2
  13. package/dist/style.js +1 -1
  14. package/dist/tshost-declarations.d.ts +11 -5
  15. package/dist/tshost-declarations.js +28 -18
  16. package/dist/tshost.d.ts +12 -1
  17. package/dist/tshost.js +23 -15
  18. package/dist/views.d.ts +7 -0
  19. package/dist/views.js +20 -0
  20. package/dist/walk.d.ts +8 -1
  21. package/dist/walk.js +16 -9
  22. package/package.json +12 -10
  23. package/dist/account.d.ts.map +0 -1
  24. package/dist/account.js.map +0 -1
  25. package/dist/build.d.ts.map +0 -1
  26. package/dist/build.js.map +0 -1
  27. package/dist/check.d.ts.map +0 -1
  28. package/dist/check.js.map +0 -1
  29. package/dist/commands.d.ts.map +0 -1
  30. package/dist/commands.js.map +0 -1
  31. package/dist/companions.d.ts +0 -18
  32. package/dist/companions.d.ts.map +0 -1
  33. package/dist/companions.js +0 -44
  34. package/dist/companions.js.map +0 -1
  35. package/dist/console.d.ts.map +0 -1
  36. package/dist/console.js.map +0 -1
  37. package/dist/declarations.d.ts.map +0 -1
  38. package/dist/declarations.js.map +0 -1
  39. package/dist/diag.d.ts.map +0 -1
  40. package/dist/diag.js.map +0 -1
  41. package/dist/frame.d.ts.map +0 -1
  42. package/dist/frame.js.map +0 -1
  43. package/dist/index.d.ts.map +0 -1
  44. package/dist/index.js.map +0 -1
  45. package/dist/init.d.ts.map +0 -1
  46. package/dist/init.js.map +0 -1
  47. package/dist/key.d.ts.map +0 -1
  48. package/dist/key.js.map +0 -1
  49. package/dist/login.d.ts.map +0 -1
  50. package/dist/login.js.map +0 -1
  51. package/dist/main.d.ts.map +0 -1
  52. package/dist/main.js.map +0 -1
  53. package/dist/open-url.d.ts.map +0 -1
  54. package/dist/open-url.js.map +0 -1
  55. package/dist/register.d.ts.map +0 -1
  56. package/dist/register.js.map +0 -1
  57. package/dist/run.d.ts.map +0 -1
  58. package/dist/run.js.map +0 -1
  59. package/dist/skill.d.ts.map +0 -1
  60. package/dist/skill.js.map +0 -1
  61. package/dist/style.d.ts.map +0 -1
  62. package/dist/style.js.map +0 -1
  63. package/dist/tshost-declarations.d.ts.map +0 -1
  64. package/dist/tshost-declarations.js.map +0 -1
  65. package/dist/tshost.d.ts.map +0 -1
  66. package/dist/tshost.js.map +0 -1
  67. package/dist/walk.d.ts.map +0 -1
  68. package/dist/walk.js.map +0 -1
package/dist/build.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export interface BuildResult {
2
2
  written: string[];
3
3
  errors: string[];
4
+ /** non-fatal: same-basename .ts + .tsi pairs (spec decision 5) */
5
+ warnings: string[];
4
6
  }
5
7
  export declare function cmdBuild(dirArg?: string, outArg?: string): Promise<BuildResult>;
6
8
  //# sourceMappingURL=build.d.ts.map
package/dist/build.js CHANGED
@@ -2,13 +2,13 @@ import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import remappingImport from "@ampproject/remapping";
5
- import { Codes } from "@nola-lang/ast";
6
- import { companionSourceCandidates, companionSpecifierFor, compileCompanion, compileNola } from "@nola-lang/compiler";
7
5
  import { bundleSelfConfiguringConfig, findProjectRoot, loadBuildOptions, loadCompilerOptions, } from "@nola-lang/node-loader";
8
6
  import { transformSync } from "esbuild";
7
+ import { createProjectDeriver } from "./derive.js";
9
8
  import { printDiagnostics } from "./diag.js";
10
9
  import { emitDeclarationTexts } from "./tshost-declarations.js";
11
- import { walkProject } from "./walk.js";
10
+ import { danglingViewErrors } from "./views.js";
11
+ import { siblingWarning, walkProject } from "./walk.js";
12
12
  const remapping = remappingImport;
13
13
  /** First line of the emitted config bundle; the overwrite guard keys on it. */
14
14
  const CONFIG_BUNDLE_MARKER = "// generated by nola build — self-configuring Nola config bundle";
@@ -23,20 +23,15 @@ export async function cmdBuild(dirArg = ".", outArg = "dist") {
23
23
  const written = [];
24
24
  const errors = [];
25
25
  const lowered = [];
26
- const metas = [];
27
- const { tsi, reserved } = await walkProject(dir);
28
- for (const file of reserved) {
29
- errors.push(`${file} ${Codes.ReservedCompanionPath}: the *.nola.* filename namespace is reserved for generated companion modules — rename this file`);
30
- }
31
- if (errors.length > 0)
32
- return { written, errors };
26
+ const { tsi, siblings } = await walkProject(dir);
27
+ const warnings = siblings.map(siblingWarning);
33
28
  // App builds ship the config: bundle nola.config.ts (relative imports
34
29
  // inlined, packages external) with a self-configuring wrapper, emitted
35
30
  // first so a bad config fails the build before any compilation.
36
31
  if (wireConfig) {
37
32
  if (existsSync(configOutPath) && !(await readFile(configOutPath, "utf8")).startsWith(CONFIG_BUNDLE_MARKER)) {
38
33
  errors.push(`${configOutPath}: exists and was not generated by nola build — refusing to overwrite. Is another toolchain compiling nola.config.ts into --out?`);
39
- return { written, errors };
34
+ return { written, errors, warnings };
40
35
  }
41
36
  try {
42
37
  const bundled = await bundleSelfConfiguringConfig(configPath);
@@ -46,81 +41,82 @@ export async function cmdBuild(dirArg = ".", outArg = "dist") {
46
41
  }
47
42
  catch (err) {
48
43
  errors.push(err instanceof Error ? err.message : String(err));
49
- return { written, errors };
44
+ return { written, errors, warnings };
50
45
  }
51
46
  }
52
- for (const file of tsi) {
53
- const source = await readFile(file, "utf8");
54
- const result = compileNola(source, file, { sourceRoot, underivableContextType });
55
- if (result.diagnostics.length > 0) {
56
- errors.push(printDiagnostics(result.diagnostics, () => source));
57
- continue;
58
- }
59
- lowered.push({ file, loweredCode: result.code });
60
- metas.push(result.meta.companions);
61
- // The wiring import is appended to the TRANSFORM INPUT only — the
62
- // declarations pass reuses result.code and must never see it. EOF keeps
63
- // positions intact; the appended line has no counterpart in result.map,
64
- // so it stays unmapped after the merge.
65
- const outPath = `${join(out, relative(dir, file))}.js`;
66
- let emitCode = result.code;
67
- if (wireConfig) {
68
- const rel = relative(dirname(outPath), configOutPath).replace(/\\/g, "/");
69
- emitCode = `${result.code}\nimport ${JSON.stringify(rel.startsWith(".") ? rel : `./${rel}`)};\n`;
70
- }
71
- const js = transformSync(emitCode, { loader: "ts", format: "esm", sourcemap: "external", sourcefile: file });
72
- let consumed = false;
73
- const merged = remapping(js.map, () => {
74
- if (consumed)
75
- return null;
76
- consumed = true;
77
- return result.map;
78
- });
79
- await mkdir(dirname(outPath), { recursive: true });
80
- await writeFile(outPath, `${js.code}\n//# sourceMappingURL=${basename(outPath)}.map\n`);
81
- await writeFile(`${outPath}.map`, merged.toString());
82
- written.push(outPath, `${outPath}.map`);
47
+ const deriver = createProjectDeriver(dir, sourceRoot, underivableContextType);
48
+ try {
49
+ return await buildWith(deriver);
83
50
  }
84
- // Companion worklist: every reachable type source gets one real module in
85
- // dist, named by the same forward transform the lowered imports use — so
86
- // production output needs no loader at all.
87
- const emittedCompanions = new Set();
88
- const companionQueue = lowered.flatMap((entry, i) => (metas[i] ?? []).map((specifier) => ({ importer: entry.file, specifier })));
89
- while (companionQueue.length > 0) {
90
- const { importer, specifier } = companionQueue.shift();
91
- const src = companionSourceCandidates(specifier)
92
- .map((c) => resolve(dirname(importer), c))
93
- .find((p) => existsSync(p));
94
- if (!src) {
95
- errors.push(`${importer} ${Codes.CompanionUnavailable}: cannot locate the type source for "${specifier}"`);
96
- continue;
51
+ finally {
52
+ deriver.dispose();
53
+ }
54
+ async function buildWith(deriver) {
55
+ for (const file of tsi) {
56
+ const source = await readFile(file, "utf8");
57
+ // phase 1 + the checker pass (emit 15): the appendix accessors carry real bodies from here on
58
+ const result = deriver.lower(file, source);
59
+ if (result.diagnostics.length > 0) {
60
+ errors.push(printDiagnostics(result.diagnostics, () => source));
61
+ continue;
62
+ }
63
+ lowered.push({ file, loweredCode: result.code });
64
+ errors.push(...danglingViewErrors(file, result.meta.views));
65
+ // The wiring import is appended to the TRANSFORM INPUT only — the
66
+ // declarations pass reuses result.code and must never see it. EOF keeps
67
+ // positions intact; the appended line has no counterpart in result.map,
68
+ // so it stays unmapped after the merge.
69
+ const outPath = `${join(out, relative(dir, file))}.js`;
70
+ let emitCode = result.code;
71
+ if (wireConfig) {
72
+ const rel = relative(dirname(outPath), configOutPath).replace(/\\/g, "/");
73
+ emitCode = `${result.code}\nimport ${JSON.stringify(rel.startsWith(".") ? rel : `./${rel}`)};\n`;
74
+ }
75
+ const js = transformSync(emitCode, { loader: "ts", format: "esm", sourcemap: "external", sourcefile: file });
76
+ let consumed = false;
77
+ const merged = remapping(js.map, () => {
78
+ if (consumed)
79
+ return null;
80
+ consumed = true;
81
+ return result.map;
82
+ });
83
+ await mkdir(dirname(outPath), { recursive: true });
84
+ await writeFile(outPath, `${js.code}\n//# sourceMappingURL=${basename(outPath)}.map\n`);
85
+ await writeFile(`${outPath}.map`, merged.toString());
86
+ written.push(outPath, `${outPath}.map`);
97
87
  }
98
- if (emittedCompanions.has(src))
99
- continue;
100
- emittedCompanions.add(src);
101
- const companion = compileCompanion(await readFile(src, "utf8"), src, { sourceRoot });
102
- if (companion.code === "" || companion.diagnostics.length > 0) {
103
- errors.push(printDiagnostics(companion.diagnostics, () => ""));
104
- continue;
88
+ const decl = await emitDeclarationTexts(lowered, sourceRoot, dir, { deriveView: deriver.viewCode });
89
+ for (const [file, declarationText] of decl.tsi) {
90
+ // Next to the built .js in --out (report.tsi.js + report.tsi.d.ts pair
91
+ // under NodeNext), NEVER adjacent to the source: the editor serves
92
+ // .tsi virtually, and an on-disk declaration would only shadow it.
93
+ const dtsPath = `${join(out, relative(dir, file))}.d.ts`;
94
+ await mkdir(dirname(dtsPath), { recursive: true });
95
+ await writeFile(dtsPath, declarationText);
96
+ written.push(dtsPath);
105
97
  }
106
- const js = transformSync(companion.code, { loader: "ts", format: "esm" });
107
- const outName = companionSpecifierFor(basename(src));
108
- const outPath = join(out, relative(dir, join(dirname(src), outName)));
109
- await mkdir(dirname(outPath), { recursive: true });
110
- await writeFile(outPath, js.code);
111
- written.push(outPath);
112
- companionQueue.push(...companion.companions.map((c) => ({ importer: src, specifier: c })));
113
- }
114
- const texts = await emitDeclarationTexts(lowered, metas, sourceRoot, dir);
115
- for (const [file, declarationText] of texts) {
116
- // Next to the built .js in --out (report.tsi.js + report.tsi.d.ts pair
117
- // under NodeNext), NEVER adjacent to the source: the editor serves
118
- // .tsi virtually, and an on-disk declaration would only shadow it.
119
- const dtsPath = `${join(out, relative(dir, file))}.d.ts`;
120
- await mkdir(dirname(dtsPath), { recursive: true });
121
- await writeFile(dtsPath, declarationText);
122
- written.push(dtsPath);
98
+ // Every VIEW the program reached becomes a real `<base>.tsi.js` +
99
+ // `<base>.tsi.d.ts` pair beside the lowered output — the NodeNext pair real
100
+ // .tsi files get — so production output needs no loader at all. The view
101
+ // re-exports `./<base>.js`, so the plain module compiles into the same tree.
102
+ for (const [viewSource, declarationText] of decl.views) {
103
+ const src = resolve(viewSource); // tshost keys views by posix path; `written` stays platform-native
104
+ const base = basename(src).replace(/\.d\.ts$|\.ts$/, "");
105
+ const outDir = join(out, relative(dir, dirname(src)));
106
+ const viewCode = deriver.viewCode(src);
107
+ if (viewCode === undefined) {
108
+ errors.push(`${src}: the view of this module could not be derived (see the diagnostics above)`);
109
+ continue;
110
+ }
111
+ const js = transformSync(viewCode, { loader: "ts", format: "esm" });
112
+ await mkdir(outDir, { recursive: true });
113
+ const jsPath = join(outDir, `${base}.tsi.js`);
114
+ const dtsPath = join(outDir, `${base}.tsi.d.ts`);
115
+ await writeFile(jsPath, js.code);
116
+ await writeFile(dtsPath, declarationText);
117
+ written.push(jsPath, dtsPath);
118
+ }
119
+ return { written, errors, warnings };
123
120
  }
124
- return { written, errors };
125
121
  }
126
122
  //# sourceMappingURL=build.js.map
package/dist/check.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare function cmdCheck(dirArg?: string): Promise<{
2
2
  errors: string[];
3
+ warnings: string[];
3
4
  }>;
4
5
  //# sourceMappingURL=check.d.ts.map
package/dist/check.js CHANGED
@@ -1,71 +1,72 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
4
- import { Codes } from "@nola-lang/ast";
5
- import { compileNola } from "@nola-lang/compiler";
6
4
  import { findProjectRoot, loadCompilerOptions } from "@nola-lang/node-loader";
7
5
  import ts from "typescript";
8
- import { collectCompanionEntries } from "./companions.js";
6
+ import { createProjectDeriver } from "./derive.js";
9
7
  import { printDiagnostics } from "./diag.js";
10
8
  import { createLoweredProgram } from "./tshost.js";
11
- import { walkProject } from "./walk.js";
9
+ import { danglingViewErrors } from "./views.js";
10
+ import { siblingWarning, walkProject } from "./walk.js";
12
11
  export async function cmdCheck(dirArg = ".") {
13
12
  const dir = resolve(dirArg);
14
13
  // Check the same text `nola build` emits, so the two can't drift.
15
14
  const sourceRoot = findProjectRoot(dir);
16
15
  const { underivableContextType } = await loadCompilerOptions(dir);
17
16
  const errors = [];
18
- const { tsi, reserved } = await walkProject(dir);
19
- for (const file of reserved) {
20
- errors.push(`${file} ${Codes.ReservedCompanionPath}: the *.nola.* filename namespace is reserved for generated companion modules — rename this file`);
21
- }
22
- if (errors.length > 0)
23
- return { errors };
17
+ const { tsi, siblings } = await walkProject(dir);
18
+ const warnings = siblings.map(siblingWarning);
24
19
  const entries = [];
25
20
  const maps = new Map();
26
- const seeds = [];
27
- for (const file of tsi) {
28
- const source = await readFile(file, "utf8");
29
- const result = compileNola(source, file, { sourceRoot, underivableContextType });
30
- if (result.diagnostics.length > 0) {
31
- errors.push(printDiagnostics(result.diagnostics, () => source));
32
- continue;
33
- }
34
- entries.push({ file, loweredCode: result.code });
35
- maps.set(file, result.map);
36
- seeds.push({ file, companions: result.meta.companions });
37
- }
38
- if (entries.length === 0)
39
- return { errors };
40
- const companionEntries = await collectCompanionEntries(seeds, sourceRoot);
41
- errors.push(...companionEntries.errors);
42
- const { program, virtualName } = createLoweredProgram(entries, dir, "check", companionEntries.virtual);
43
- const virtualToFile = new Map(entries.map((e) => [virtualName(e.file).replace(/\\/g, "/"), e.file]));
44
- for (const d of ts.getPreEmitDiagnostics(program)) {
45
- if (!d.file || d.start === undefined)
46
- continue;
47
- const fileName = d.file.fileName.replace(/\\/g, "/");
48
- const message = ts.flattenDiagnosticMessageText(d.messageText, " ");
49
- const companionSource = companionEntries.sources.get(fileName);
50
- if (companionSource) {
51
- // Diagnostics policy: never report into a file the user cannot open.
52
- errors.push(`${companionSource}:1:1 TS${d.code}: in derived type module: ${message}`);
53
- continue;
21
+ const deriver = createProjectDeriver(dir, sourceRoot, underivableContextType);
22
+ try {
23
+ for (const file of tsi) {
24
+ const source = await readFile(file, "utf8");
25
+ // phase 1 + the checker pass: NOLA2002 / NOLA2008 arrive with the lowering diagnostics
26
+ const result = deriver.lower(file, source);
27
+ if (result.diagnostics.length > 0) {
28
+ errors.push(printDiagnostics(result.diagnostics, () => source));
29
+ continue;
30
+ }
31
+ entries.push({ file, loweredCode: result.code });
32
+ maps.set(file, result.map);
33
+ errors.push(...danglingViewErrors(file, result.meta.views));
54
34
  }
55
- const original = virtualToFile.get(fileName);
56
- if (!original) {
57
- // A real on-disk file (a plain .ts project root): position it directly.
58
- const pos = d.file.getLineAndCharacterOfPosition(d.start);
59
- errors.push(`${d.file.fileName}:${pos.line + 1}:${pos.character + 1} TS${d.code}: ${message}`);
60
- continue;
35
+ if (entries.length === 0)
36
+ return { errors, warnings };
37
+ const { program, virtualName, views } = createLoweredProgram(entries, dir, "check", sourceRoot, {
38
+ deriveView: deriver.viewCode,
39
+ });
40
+ const virtualToFile = new Map(entries.map((e) => [virtualName(e.file).replace(/\\/g, "/"), e.file]));
41
+ for (const d of ts.getPreEmitDiagnostics(program)) {
42
+ if (!d.file || d.start === undefined)
43
+ continue;
44
+ const fileName = d.file.fileName.replace(/\\/g, "/");
45
+ const message = ts.flattenDiagnosticMessageText(d.messageText, " ");
46
+ const viewSource = views.get(fileName);
47
+ if (viewSource) {
48
+ // Diagnostics policy: never report into a file the user cannot open.
49
+ errors.push(`${viewSource}:1:1 TS${d.code}: in derived type module: ${message}`);
50
+ continue;
51
+ }
52
+ const original = virtualToFile.get(fileName);
53
+ if (!original) {
54
+ // A real on-disk file (a plain .ts project root): position it directly.
55
+ const pos = d.file.getLineAndCharacterOfPosition(d.start);
56
+ errors.push(`${d.file.fileName}:${pos.line + 1}:${pos.character + 1} TS${d.code}: ${message}`);
57
+ continue;
58
+ }
59
+ const generated = d.file.getLineAndCharacterOfPosition(d.start);
60
+ const tracer = new TraceMap(maps.get(original));
61
+ const pos = originalPositionFor(tracer, { line: generated.line + 1, column: generated.character });
62
+ const line = pos.line ?? generated.line + 1;
63
+ const column = (pos.column ?? generated.character) + 1;
64
+ errors.push(`${original}:${line}:${column} TS${d.code}: ${message}`);
61
65
  }
62
- const generated = d.file.getLineAndCharacterOfPosition(d.start);
63
- const tracer = new TraceMap(maps.get(original));
64
- const pos = originalPositionFor(tracer, { line: generated.line + 1, column: generated.character });
65
- const line = pos.line ?? generated.line + 1;
66
- const column = (pos.column ?? generated.character) + 1;
67
- errors.push(`${original}:${line}:${column} TS${d.code}: ${message}`);
66
+ return { errors, warnings };
67
+ }
68
+ finally {
69
+ deriver.dispose();
68
70
  }
69
- return { errors };
70
71
  }
71
72
  //# sourceMappingURL=check.js.map
@@ -6,6 +6,7 @@ import { type CliIo, type CommandSpec, type Palette } from "create-nola-lang";
6
6
  export declare function report(label: string, result: {
7
7
  written?: string[];
8
8
  errors: string[];
9
+ warnings?: string[];
9
10
  }, separator: string, io?: CliIo, p?: Palette): number;
10
11
  export declare const COMMANDS: readonly CommandSpec[];
11
12
  //# sourceMappingURL=commands.d.ts.map
package/dist/commands.js CHANGED
@@ -18,6 +18,9 @@ const consoleIo = { log: (m) => console.log(m), error: (m) => console.error(m) }
18
18
  * summary (exit 0): the file count when it writes, "no errors" otherwise.
19
19
  */
20
20
  export function report(label, result, separator, io = consoleIo, p = ansi) {
21
+ // Non-fatal (a same-basename .ts + .tsi pair): stderr, no exit-code change.
22
+ for (const warning of result.warnings ?? [])
23
+ io.error(`warning: ${warning}`);
21
24
  if (result.errors.length > 0) {
22
25
  io.error(styleDiagnostics(result.errors.join(separator), p));
23
26
  return 1;
@@ -53,10 +56,10 @@ export const COMMANDS = [
53
56
  }),
54
57
  defineCommand({
55
58
  name: "skill",
56
- summary: "write agent skill files (Claude Code, Cursor, Copilot, AGENTS.md) into the project",
59
+ summary: "write the Nola agent skill (.claude/skills, .agents/skills) and/or AGENTS.md into the project",
57
60
  args: "install",
58
61
  options: {
59
- agents: { type: "string", description: "claude,cursor,copilot,agents-md | all | none" },
62
+ agents: { type: "string", description: "claude,universal,agents-md | all | none" },
60
63
  force: { type: "boolean", description: "replace files stamped by another Nola version" },
61
64
  },
62
65
  run: ({ positionals: [sub], values }) => cmdSkill(sub, { agents: values.agents, force: values.force }),
@@ -1,13 +1,16 @@
1
1
  /** report.tsi → report.d.tsi.ts, adjacent — TS allowArbitraryExtensions resolves it. */
2
2
  export declare function adjacentDeclarationPath(tsiFile: string): string;
3
+ /** models.ts / api.d.ts → models.d.tsi.ts / api.d.tsi.ts: the declaration of the module's VIEW. */
4
+ export declare function adjacentViewDeclarationPath(viewSourceFile: string): string;
3
5
  /**
4
- * Emit <base>.d.tsi.ts next to each .tsi so PLAIN tsc (and framework builds
5
- * like `next build`) resolve .tsi imports via allowArbitraryExtensions.
6
- * The editor hides these when the sibling .tsi exists; `nola check` excludes
7
- * them from roots.
6
+ * Emit <base>.d.tsi.ts next to each .tsi — and next to each plain module
7
+ * reached as a VIEW — so PLAIN tsc (and framework builds like `next build`)
8
+ * resolve `.tsi` imports via allowArbitraryExtensions. The editor hides these
9
+ * when the sibling .tsi exists; `nola check` excludes them from roots.
8
10
  */
9
11
  export declare function emitAdjacentDeclarations(dirArg?: string): Promise<{
10
12
  written: string[];
11
13
  errors: string[];
14
+ warnings: string[];
12
15
  }>;
13
16
  //# sourceMappingURL=declarations.d.ts.map
@@ -1,20 +1,24 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
- import { Codes } from "@nola-lang/ast";
4
- import { compileNola } from "@nola-lang/compiler";
5
3
  import { findProjectRoot, loadCompilerOptions } from "@nola-lang/node-loader";
4
+ import { createProjectDeriver } from "./derive.js";
6
5
  import { printDiagnostics } from "./diag.js";
7
6
  import { emitDeclarationTexts } from "./tshost-declarations.js";
8
- import { walkProject } from "./walk.js";
7
+ import { danglingViewErrors } from "./views.js";
8
+ import { siblingWarning, walkProject } from "./walk.js";
9
9
  /** report.tsi → report.d.tsi.ts, adjacent — TS allowArbitraryExtensions resolves it. */
10
10
  export function adjacentDeclarationPath(tsiFile) {
11
11
  return `${tsiFile.slice(0, -".tsi".length)}.d.tsi.ts`;
12
12
  }
13
+ /** models.ts / api.d.ts → models.d.tsi.ts / api.d.tsi.ts: the declaration of the module's VIEW. */
14
+ export function adjacentViewDeclarationPath(viewSourceFile) {
15
+ return `${viewSourceFile.replace(/\.d\.ts$|\.ts$/, "")}.d.tsi.ts`;
16
+ }
13
17
  /**
14
- * Emit <base>.d.tsi.ts next to each .tsi so PLAIN tsc (and framework builds
15
- * like `next build`) resolve .tsi imports via allowArbitraryExtensions.
16
- * The editor hides these when the sibling .tsi exists; `nola check` excludes
17
- * them from roots.
18
+ * Emit <base>.d.tsi.ts next to each .tsi — and next to each plain module
19
+ * reached as a VIEW — so PLAIN tsc (and framework builds like `next build`)
20
+ * resolve `.tsi` imports via allowArbitraryExtensions. The editor hides these
21
+ * when the sibling .tsi exists; `nola check` excludes them from roots.
18
22
  */
19
23
  export async function emitAdjacentDeclarations(dirArg = ".") {
20
24
  const dir = resolve(dirArg);
@@ -22,30 +26,38 @@ export async function emitAdjacentDeclarations(dirArg = ".") {
22
26
  const { underivableContextType } = await loadCompilerOptions(dir);
23
27
  const written = [];
24
28
  const errors = [];
25
- const { tsi, reserved } = await walkProject(dir);
26
- for (const file of reserved) {
27
- errors.push(`${file} ${Codes.ReservedCompanionPath}: the *.nola.* filename namespace is reserved for generated companion modules — rename this file`);
28
- }
29
- if (errors.length > 0)
30
- return { written, errors };
29
+ const { tsi, siblings } = await walkProject(dir);
30
+ const warnings = siblings.map(siblingWarning);
31
31
  const lowered = [];
32
- const metas = [];
33
- for (const file of tsi) {
34
- const source = await readFile(file, "utf8");
35
- const result = compileNola(source, file, { sourceRoot, underivableContextType });
36
- if (result.diagnostics.length > 0) {
37
- errors.push(printDiagnostics(result.diagnostics, () => source));
38
- continue;
32
+ const deriver = createProjectDeriver(dir, sourceRoot, underivableContextType);
33
+ let decl;
34
+ try {
35
+ for (const file of tsi) {
36
+ const source = await readFile(file, "utf8");
37
+ const result = deriver.lower(file, source);
38
+ if (result.diagnostics.length > 0) {
39
+ errors.push(printDiagnostics(result.diagnostics, () => source));
40
+ continue;
41
+ }
42
+ lowered.push({ file, loweredCode: result.code });
43
+ errors.push(...danglingViewErrors(file, result.meta.views));
39
44
  }
40
- lowered.push({ file, loweredCode: result.code });
41
- metas.push(result.meta.companions);
45
+ decl = await emitDeclarationTexts(lowered, sourceRoot, dir, { deriveView: deriver.viewCode });
46
+ }
47
+ finally {
48
+ deriver.dispose();
42
49
  }
43
- const texts = await emitDeclarationTexts(lowered, metas, sourceRoot, dir);
44
- for (const [file, text] of texts) {
50
+ for (const [file, text] of decl.tsi) {
45
51
  const dtsPath = adjacentDeclarationPath(file);
46
52
  await writeFile(dtsPath, text);
47
53
  written.push(dtsPath);
48
54
  }
49
- return { written, errors };
55
+ for (const [src, text] of decl.views) {
56
+ // tshost keys views by posix path; `resolve` restores the platform form for `written`
57
+ const dtsPath = adjacentViewDeclarationPath(resolve(src));
58
+ await writeFile(dtsPath, text);
59
+ written.push(dtsPath);
60
+ }
61
+ return { written, errors, warnings };
50
62
  }
51
63
  //# sourceMappingURL=declarations.js.map
@@ -0,0 +1,21 @@
1
+ import { type CompileResult } from "@nola-lang/compiler";
2
+ import type { UnderivableContextTypeMode } from "@nola-lang/core";
3
+ import { type DerivationService } from "@nola-lang/derive";
4
+ /**
5
+ * The checker-backed derivation seam of `nola build` / `nola check` /
6
+ * `nola declarations` (emit 15): one DerivationService per command, phase 1
7
+ * → answers → finalize for every `.tsi`, and the finalized view of every plain
8
+ * module the lowered program reaches (fed to tshost through its `deriveView`
9
+ * hook, so the program type-checks the REAL bodies — the UnsupportedType
10
+ * elaboration included — and the emitted `.tsi.js` of a view is complete).
11
+ */
12
+ export interface ProjectDeriver {
13
+ readonly service: DerivationService;
14
+ /** phase 1 + derive + finalize; `diagnostics` carries NOLA2002 / NOLA2008 next to the parse/lower ones */
15
+ lower(file: string, source: string): CompileResult;
16
+ /** finalized code of the view of `source` (x.ts / x.d.ts), memoized; undefined when the source does not parse */
17
+ viewCode(source: string): string | undefined;
18
+ dispose(): void;
19
+ }
20
+ export declare function createProjectDeriver(projectDir: string, sourceRoot: string, underivableContextType: UnderivableContextTypeMode): ProjectDeriver;
21
+ //# sourceMappingURL=derive.d.ts.map
package/dist/derive.js ADDED
@@ -0,0 +1,27 @@
1
+ import { compileNola, finalizeDerivations } from "@nola-lang/compiler";
2
+ import { createDerivationService } from "@nola-lang/derive";
3
+ export function createProjectDeriver(projectDir, sourceRoot, underivableContextType) {
4
+ const service = createDerivationService({ projectRoot: projectDir, sourceRoot, underivableContextType });
5
+ const views = new Map();
6
+ return {
7
+ service,
8
+ lower(file, source) {
9
+ const phase1 = compileNola(source, file, { sourceRoot, underivableContextType });
10
+ if (phase1.diagnostics.length > 0)
11
+ return phase1;
12
+ return finalizeDerivations(phase1, service.derive(file, phase1).answers, file);
13
+ },
14
+ viewCode(source) {
15
+ const key = source.replace(/\\/g, "/");
16
+ if (!views.has(key)) {
17
+ const view = service.deriveView(source);
18
+ views.set(key, view.code === "" || view.diagnostics.length > 0 ? undefined : view.code);
19
+ }
20
+ return views.get(key);
21
+ },
22
+ dispose() {
23
+ service.dispose();
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=derive.js.map
package/dist/skill.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** `nola skill install` — write agent skill adapters into the current project. */
1
+ /** `nola skill install` — write the agent skill (.claude/skills, .agents/skills) and/or AGENTS.md into the current project. */
2
2
  export declare function cmdSkill(sub: string | undefined, opts: {
3
3
  agents?: string;
4
4
  force?: boolean;
package/dist/skill.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { runSkillInstall } from "create-nola-lang";
2
- const USAGE = "nola skill install [--agents claude,cursor,copilot,agents-md | all | none] [--force]";
3
- /** `nola skill install` — write agent skill adapters into the current project. */
2
+ const USAGE = "nola skill install [--agents claude,universal,agents-md | all | none] [--force]";
3
+ /** `nola skill install` — write the agent skill (.claude/skills, .agents/skills) and/or AGENTS.md into the current project. */
4
4
  export async function cmdSkill(sub, opts) {
5
5
  if (sub !== "install") {
6
6
  console.log(USAGE);
package/dist/style.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // `path:line:col CODE: message` (build/check/declarations), or `path CODE: message`
2
- // for the position-less reserved-companion refusal. The path is everything
2
+ // for a position-less refusal such as a dangling view import (NOLA2007). The path is everything
3
3
  // before the last `:line:col` — a Windows drive letter carries its own colon.
4
4
  const HEAD = /^(.+?)(:\d+:\d+)? ((?:NOLA|TS)\d+): /;
5
5
  const GUTTER = /^(\d+ \|)( .*)?$/;
@@ -1,8 +1,14 @@
1
- import { type LoweredEntry } from "./tshost.js";
1
+ import { type LoweredEntry, type LoweredProgramHooks } from "./tshost.js";
2
+ export interface DeclarationTexts {
3
+ /** .tsi source path -> its declaration text */
4
+ tsi: Map<string, string>;
5
+ /** viewed plain-module source path (x.ts / x.d.ts) -> the view's declaration text */
6
+ views: Map<string, string>;
7
+ }
2
8
  /**
3
- * Declaration text per lowered .tsi, from one shared lowered program —
4
- * extracted from cmdBuild so `nola build` (--out pair) and
5
- * `nola declarations` (adjacent d.tsi.ts) cannot drift.
9
+ * Declaration text per lowered .tsi — and per VIEW the program reached —
10
+ * from one shared lowered program, extracted from cmdBuild so `nola build`
11
+ * (--out pairs) and `nola declarations` (adjacent d.tsi.ts) cannot drift.
6
12
  */
7
- export declare function emitDeclarationTexts(lowered: LoweredEntry[], metas: string[][], sourceRoot: string, projectDir: string): Promise<Map<string, string>>;
13
+ export declare function emitDeclarationTexts(lowered: LoweredEntry[], sourceRoot: string, projectDir: string, hooks?: LoweredProgramHooks): Promise<DeclarationTexts>;
8
14
  //# sourceMappingURL=tshost-declarations.d.ts.map