create-nola-lang 0.1.11 → 0.1.13
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 +19 -13
- package/dist/agents.d.ts +13 -1
- package/dist/agents.js +107 -9
- package/dist/checkout.d.ts +20 -0
- package/dist/checkout.js +48 -1
- package/dist/{chunk-MAULRZZD.js → chunk-LUSVIOAN.js} +248 -77
- package/dist/flow.d.ts +19 -5
- package/dist/flow.js +139 -51
- package/dist/ide.d.ts +2 -2
- package/dist/ide.js +12 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/launch.d.ts +2 -0
- package/dist/launch.js +1 -0
- package/dist/main.js +1 -1
- package/dist/providers.d.ts +7 -4
- package/dist/providers.js +3 -2
- package/dist/registry.d.ts +30 -2
- package/dist/registry.js +34 -7
- package/dist/scaffold.d.ts +20 -6
- package/dist/scaffold.js +42 -13
- package/package.json +1 -1
- package/skills/nola/SKILL.md +8 -5
- package/skills/nola/references/config.md +22 -13
- package/skills/nola/references/patterns.md +92 -17
- package/skills/nola/references/pitfalls.md +66 -19
- package/skills/nola/references/syntax.md +98 -28
- package/templates/_providers/typesafe.config.ts +18 -0
- package/templates/feature-extraction/README.md +20 -0
- package/templates/feature-extraction/nola.config.ts +14 -0
- package/templates/feature-extraction/nola.replay.jsonl +2 -0
- package/templates/feature-extraction/package.json +22 -0
- package/templates/feature-extraction/src/main.tsi +22 -0
- package/templates/function-calling/README.md +21 -0
- package/templates/function-calling/nola.config.ts +14 -0
- package/templates/function-calling/nola.replay.jsonl +1 -0
- package/templates/function-calling/package.json +22 -0
- package/templates/function-calling/src/main.tsi +12 -0
- package/templates/function-calling/src/tickets.ts +17 -0
- package/templates/function-calling/tsconfig.json +12 -0
- package/templates/{starter → typescript-interop}/nola.config.ts +1 -1
- package/templates/{starter → typescript-interop}/src/person.tsi +1 -1
- package/templates/typescript-interop/tsconfig.json +12 -0
- /package/templates/{starter → feature-extraction}/tsconfig.json +0 -0
- /package/templates/{starter → typescript-interop}/README.md +0 -0
- /package/templates/{starter → typescript-interop}/nola.replay.jsonl +0 -0
- /package/templates/{starter → typescript-interop}/package.json +0 -0
- /package/templates/{starter → typescript-interop}/src/main.ts +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/providers.ts
|
|
7
7
|
var PROVIDERS = [
|
|
8
|
-
{ id: "nola", label: "
|
|
8
|
+
{ id: "nola", label: "nola: dev", hint: "25 free hosted runs, no API key required, suited for dev experiments", model: '"nola"' },
|
|
9
9
|
{ id: "openai", label: "OpenAI", hint: 'openai("gpt-5-mini"), reads OPENAI_API_KEY', envVar: "OPENAI_API_KEY", model: 'openai("gpt-5-mini")' },
|
|
10
10
|
{
|
|
11
11
|
id: "anthropic",
|
|
@@ -15,7 +15,8 @@ var PROVIDERS = [
|
|
|
15
15
|
model: 'anthropic("claude-sonnet-4-5")'
|
|
16
16
|
},
|
|
17
17
|
{ id: "google", label: "Gemini", hint: 'google("gemini-2.5-flash"), reads GEMINI_API_KEY', envVar: "GEMINI_API_KEY", model: 'google("gemini-2.5-flash")' },
|
|
18
|
-
{ id: "
|
|
18
|
+
{ id: "typesafe", label: "typesafe.ai", hint: "typesafe(), reads TYPESAFE_API_KEY", envVar: "TYPESAFE_API_KEY", model: "typesafe()" },
|
|
19
|
+
{ id: "none", label: "Skip for now", hint: "the builtin templates run offline from a replay ledger; pick a model in nola.config.ts later" }
|
|
19
20
|
];
|
|
20
21
|
function providerById(id2) {
|
|
21
22
|
return PROVIDERS.find((p) => p.id === id2);
|
|
@@ -34,7 +35,7 @@ import { join as join2 } from "node:path";
|
|
|
34
35
|
|
|
35
36
|
// src/checkout.ts
|
|
36
37
|
import { existsSync } from "node:fs";
|
|
37
|
-
import { mkdir, readFile, rm, symlink } from "node:fs/promises";
|
|
38
|
+
import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
38
39
|
import { dirname, join } from "node:path";
|
|
39
40
|
import { fileURLToPath } from "node:url";
|
|
40
41
|
async function checkoutRoot() {
|
|
@@ -71,6 +72,30 @@ async function linkCheckoutPackages(dir, root) {
|
|
|
71
72
|
}
|
|
72
73
|
return linked;
|
|
73
74
|
}
|
|
75
|
+
var CHECKOUT_DIST_SKIP_GLOB = "**/packages/*/dist/**";
|
|
76
|
+
async function skipCheckoutDistInLaunch(dir) {
|
|
77
|
+
const path = join(dir, ".vscode", "launch.json");
|
|
78
|
+
if (!existsSync(path)) return false;
|
|
79
|
+
let launch;
|
|
80
|
+
try {
|
|
81
|
+
launch = JSON.parse(await readFile(path, "utf8"));
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(launch.configurations)) return false;
|
|
86
|
+
let changed = false;
|
|
87
|
+
for (const config of launch.configurations) {
|
|
88
|
+
if (typeof config !== "object" || config === null) continue;
|
|
89
|
+
const skipFiles = Array.isArray(config.skipFiles) ? config.skipFiles : [];
|
|
90
|
+
if (skipFiles.includes(CHECKOUT_DIST_SKIP_GLOB)) continue;
|
|
91
|
+
config.skipFiles = [...skipFiles, CHECKOUT_DIST_SKIP_GLOB];
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
if (!changed) return false;
|
|
95
|
+
await writeFile(path, `${JSON.stringify(launch, null, 2)}
|
|
96
|
+
`);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
74
99
|
|
|
75
100
|
// src/examples.ts
|
|
76
101
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set(["node_modules", "dist"]);
|
|
@@ -143,52 +168,83 @@ async function fetchExampleFromGitHub(name, version, fetchImpl = fetch) {
|
|
|
143
168
|
}
|
|
144
169
|
|
|
145
170
|
// src/registry.ts
|
|
171
|
+
var DEFAULT_ENTRY = "src/main.ts";
|
|
146
172
|
var TEMPLATES = [
|
|
147
|
-
{ name: "
|
|
173
|
+
{ name: "feature-extraction", label: "extract typed data from context", source: "builtin", entry: "src/main.tsi" },
|
|
174
|
+
{
|
|
175
|
+
name: "function-calling",
|
|
176
|
+
label: "call an async function from a TypeScript file",
|
|
177
|
+
source: "builtin",
|
|
178
|
+
entry: "src/main.tsi"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "typescript-interop",
|
|
182
|
+
label: "an infer function example, imported and awaited from plain TypeScript",
|
|
183
|
+
source: "builtin"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "triage-ticket",
|
|
187
|
+
label: "ticket triage on a non-chat model: literal unions and booleans as typed questions",
|
|
188
|
+
source: "example",
|
|
189
|
+
featured: true,
|
|
190
|
+
entry: "src/main.tsi",
|
|
191
|
+
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" }
|
|
192
|
+
},
|
|
148
193
|
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin" },
|
|
149
194
|
{ name: "file-ticket", label: "call intents: the model fills a function's arguments", source: "example" },
|
|
150
195
|
{ name: "extract-resume", label: "nested arrays of objects, JSDoc schema descriptions", source: "example" },
|
|
151
196
|
{ name: "extract-invoice", label: "same-file type references, optional fields", source: "example" },
|
|
152
197
|
{ name: "classify-message", label: "closed label sets: union alias, string enum, inline union", source: "example" },
|
|
153
198
|
{ name: "chain-of-thought", label: "two asks sharing accumulating context", source: "example" },
|
|
154
|
-
{ name: "research-notes", label: "TS control flow orchestrating nola functions", source: "example" }
|
|
155
|
-
{
|
|
156
|
-
name: "triage-ticket",
|
|
157
|
-
label: "ticket triage: literal unions and booleans as typed questions",
|
|
158
|
-
source: "example",
|
|
159
|
-
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" }
|
|
160
|
-
}
|
|
199
|
+
{ name: "research-notes", label: "TS control flow orchestrating nola functions", source: "example" }
|
|
161
200
|
];
|
|
201
|
+
function featuredNames() {
|
|
202
|
+
return TEMPLATES.filter((t) => t.source === "builtin" || t.featured).map((t) => t.name);
|
|
203
|
+
}
|
|
204
|
+
function exampleNames() {
|
|
205
|
+
return TEMPLATES.filter((t) => t.source === "example" && !t.featured).map((t) => t.name);
|
|
206
|
+
}
|
|
162
207
|
function templateByName(name) {
|
|
163
208
|
return TEMPLATES.find((t) => t.name === name);
|
|
164
209
|
}
|
|
165
210
|
function templateNames() {
|
|
166
211
|
return TEMPLATES.map((t) => t.name);
|
|
167
212
|
}
|
|
213
|
+
function entryFile(template) {
|
|
214
|
+
return (template === void 0 ? void 0 : templateByName(template)?.entry) ?? DEFAULT_ENTRY;
|
|
215
|
+
}
|
|
168
216
|
|
|
169
217
|
// src/scaffold.ts
|
|
170
218
|
import { existsSync as existsSync3 } from "node:fs";
|
|
171
|
-
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, writeFile } from "node:fs/promises";
|
|
219
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
172
220
|
import { basename, join as join3, resolve } from "node:path";
|
|
173
221
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
174
222
|
var TEMPLATES_DIR = fileURLToPath2(new URL("../templates/", import.meta.url));
|
|
175
223
|
function providerConfigUrl(provider) {
|
|
176
224
|
return new URL(`../templates/_providers/${provider}.config.ts`, import.meta.url);
|
|
177
225
|
}
|
|
226
|
+
function vendorEnvVar(template, provider) {
|
|
227
|
+
return providerById(provider)?.envVar ?? (provider === "none" ? templateByName(template)?.provider?.envVar : void 0);
|
|
228
|
+
}
|
|
229
|
+
function envExample(envVar) {
|
|
230
|
+
return `# Copy to .env and fill in \u2014 \`nola run\` applies .env before evaluating nola.config.ts.
|
|
231
|
+
${envVar}=
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
178
234
|
var RENAMES = { _gitignore: ".gitignore" };
|
|
179
235
|
var GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
180
236
|
async function withRecommendedGitignore(files) {
|
|
181
237
|
if (!files.has(".gitignore")) files.set(".gitignore", await readFile3(GITIGNORE_URL, "utf8"));
|
|
182
238
|
return files;
|
|
183
239
|
}
|
|
184
|
-
var SUBSTITUTED = /* @__PURE__ */ new Set(["package.json", "README.md", "main.ts"]);
|
|
240
|
+
var SUBSTITUTED = /* @__PURE__ */ new Set(["package.json", "README.md", "main.ts", "main.tsi"]);
|
|
185
241
|
function nextStepsComment(ide, template) {
|
|
186
242
|
if (ide === "vscode") {
|
|
187
|
-
const breakpointIn = template === "
|
|
243
|
+
const breakpointIn = templateByName(template)?.entry ? "on the `ask` line below" : template === "typescript-interop" ? "in src/person.tsi" : "in your .tsi file";
|
|
188
244
|
return [
|
|
189
245
|
"// Next steps in VS Code:",
|
|
190
246
|
"// 1. Press F5 to run this file (.vscode/launch.json is already set up).",
|
|
191
|
-
`// 2. Set a breakpoint
|
|
247
|
+
`// 2. Set a breakpoint ${breakpointIn} and press F5 again to step through the ask.`,
|
|
192
248
|
'// 3. Install the recommended "Nola" extension when VS Code offers it \u2014 IntelliSense,',
|
|
193
249
|
"// go to definition and diagnostics inside .tsi files."
|
|
194
250
|
].join("\n");
|
|
@@ -203,7 +259,7 @@ var OFFLINE_ONLY = /* @__PURE__ */ new Set(["nola.replay.jsonl"]);
|
|
|
203
259
|
var README_NOTES = {
|
|
204
260
|
offline: {
|
|
205
261
|
START_NOTE: "works offline, no API key needed",
|
|
206
|
-
PROVIDER_NOTE: '
|
|
262
|
+
PROVIDER_NOTE: 'This project runs offline: `nola.config.ts` replays answers from the committed\n`nola.replay.jsonl` ledger. The ledger is keyed by the exact prompt, so once\nyou edit the `.tsi` file or add your own asks, switch the config to a real\nmodel (see the comment in `nola.config.ts`): `model: "nola"` with a key from\n`npx nola-lang key` (25 free runs), or your own provider and its key.'
|
|
207
263
|
},
|
|
208
264
|
nola: {
|
|
209
265
|
START_NOTE: "uses your 25 free Nola runs (key in .env)",
|
|
@@ -218,8 +274,8 @@ function readmeNotes(provider) {
|
|
|
218
274
|
return {
|
|
219
275
|
START_NOTE: `set ${def.envVar} in .env first`,
|
|
220
276
|
PROVIDER_NOTE: `\`nola.config.ts\` sets \`model: ${def.model}\` \u2014 ${def.label} serves inference with the key it reads
|
|
221
|
-
from \`${def.envVar}\`.
|
|
222
|
-
first \`npm start\`, or switch models in \`nola.config.ts\`.`
|
|
277
|
+
from \`${def.envVar}\`. Copy \`.env.example\` to \`.env\` (git-ignored; \`nola run\` applies it) and fill in
|
|
278
|
+
\`${def.envVar}\` before the first \`npm start\`, or switch models in \`nola.config.ts\`.`
|
|
223
279
|
};
|
|
224
280
|
}
|
|
225
281
|
async function ownVersion() {
|
|
@@ -236,12 +292,13 @@ async function prepareTarget(absRoot, force) {
|
|
|
236
292
|
async function scaffold(targetDir, opts = {}) {
|
|
237
293
|
const root = targetDir;
|
|
238
294
|
const absRoot = resolve(targetDir);
|
|
239
|
-
const template = opts.template ?? "
|
|
295
|
+
const template = opts.template ?? "feature-extraction";
|
|
240
296
|
const def = templateByName(template);
|
|
241
297
|
if (!def) throw new Error(`unknown template "${template}" (valid: ${templateNames().join(", ")})`);
|
|
242
298
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
243
299
|
const name = opts.name ?? basename(absRoot);
|
|
244
300
|
const version = await ownVersion();
|
|
301
|
+
const envVar = vendorEnvVar(template, opts.provider ?? "none");
|
|
245
302
|
if (def.source === "example") {
|
|
246
303
|
const dev = await devExamplesDir();
|
|
247
304
|
const exampleFiles = await withRecommendedGitignore(
|
|
@@ -249,10 +306,11 @@ async function scaffold(targetDir, opts = {}) {
|
|
|
249
306
|
);
|
|
250
307
|
const manifest = exampleFiles.get("package.json");
|
|
251
308
|
if (manifest) exampleFiles.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
309
|
+
if (envVar !== void 0 && !exampleFiles.has(".env.example")) exampleFiles.set(".env.example", envExample(envVar));
|
|
252
310
|
for (const [relPath, content] of exampleFiles) {
|
|
253
311
|
const target = join3(absRoot, relPath);
|
|
254
312
|
await mkdir2(join3(target, ".."), { recursive: true });
|
|
255
|
-
await
|
|
313
|
+
await writeFile2(target, content);
|
|
256
314
|
}
|
|
257
315
|
return { root, files: [...exampleFiles.keys()].sort() };
|
|
258
316
|
}
|
|
@@ -278,21 +336,25 @@ async function scaffold(targetDir, opts = {}) {
|
|
|
278
336
|
if (SUBSTITUTED.has(entry.name)) {
|
|
279
337
|
content = content.replaceAll("__NAME__", name).replaceAll("__VERSION__", versionRange).replaceAll("__START_NOTE__", notes.START_NOTE).replaceAll("__PROVIDER_NOTE__", notes.PROVIDER_NOTE).replaceAll("__NEXT_STEPS__", nextSteps2);
|
|
280
338
|
}
|
|
281
|
-
await
|
|
339
|
+
await writeFile2(join3(absRoot, toRel), content);
|
|
282
340
|
files.push(toRel.replaceAll("\\", "/"));
|
|
283
341
|
}
|
|
284
342
|
};
|
|
285
343
|
await copyDir(".", ".");
|
|
286
344
|
if (!files.includes(".gitignore")) {
|
|
287
|
-
await
|
|
345
|
+
await writeFile2(join3(absRoot, ".gitignore"), await readFile3(GITIGNORE_URL, "utf8"));
|
|
288
346
|
files.push(".gitignore");
|
|
289
347
|
}
|
|
348
|
+
if (envVar !== void 0) {
|
|
349
|
+
await writeFile2(join3(absRoot, ".env.example"), envExample(envVar));
|
|
350
|
+
files.push(".env.example");
|
|
351
|
+
}
|
|
290
352
|
return { root, files: files.sort() };
|
|
291
353
|
}
|
|
292
354
|
|
|
293
355
|
// src/add.ts
|
|
294
356
|
import { existsSync as existsSync4 } from "node:fs";
|
|
295
|
-
import { readFile as readFile4, writeFile as
|
|
357
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
296
358
|
import { join as join4, resolve as resolve2 } from "node:path";
|
|
297
359
|
var EMPTY_CONFIG_URL = new URL("../templates/empty/nola.config.ts", import.meta.url);
|
|
298
360
|
function vendorOf(provider) {
|
|
@@ -337,7 +399,7 @@ async function addNola(targetDir, opts = {}) {
|
|
|
337
399
|
vendor ? `nola.config.ts already exists \u2014 set \`model: ${vendor.model}\` (${vendor.id} from @nola-lang/providers) to use ${vendor.label}` : "nola.config.ts already exists \u2014 left untouched"
|
|
338
400
|
);
|
|
339
401
|
} else {
|
|
340
|
-
await
|
|
402
|
+
await writeFile3(configPath, await readFile4(vendor ? providerConfigUrl(vendor.id) : EMPTY_CONFIG_URL, "utf8"));
|
|
341
403
|
wrote.push("nola.config.ts");
|
|
342
404
|
}
|
|
343
405
|
for (const [section, name, fixedRange] of REQUIRED) {
|
|
@@ -353,7 +415,7 @@ async function addNola(targetDir, opts = {}) {
|
|
|
353
415
|
added.push(`${name}@${range}`);
|
|
354
416
|
}
|
|
355
417
|
if (added.length > 0) {
|
|
356
|
-
await
|
|
418
|
+
await writeFile3(manifestPath, `${JSON.stringify(pkg, null, 2)}
|
|
357
419
|
`);
|
|
358
420
|
wrote.push("package.json");
|
|
359
421
|
}
|
|
@@ -362,8 +424,8 @@ async function addNola(targetDir, opts = {}) {
|
|
|
362
424
|
|
|
363
425
|
// src/agents.ts
|
|
364
426
|
import { existsSync as existsSync5 } from "node:fs";
|
|
365
|
-
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile5, rm as rm3, writeFile as
|
|
366
|
-
import { dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
|
|
427
|
+
import { lstat, mkdir as mkdir3, readdir as readdir3, readFile as readFile5, realpath, rm as rm3, symlink as symlink2, writeFile as writeFile4 } from "node:fs/promises";
|
|
428
|
+
import { dirname as dirname2, join as join5, posix, resolve as resolve3 } from "node:path";
|
|
367
429
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
368
430
|
var AGENT_IDS = ["claude", "universal", "agents-md"];
|
|
369
431
|
var SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggles, Enter confirms)";
|
|
@@ -371,6 +433,7 @@ var SKILL_DIRS = {
|
|
|
371
433
|
claude: ".claude/skills/nola",
|
|
372
434
|
universal: ".agents/skills/nola"
|
|
373
435
|
};
|
|
436
|
+
var CLAUDE_LINK_TARGET = posix.relative(posix.dirname(SKILL_DIRS.claude), SKILL_DIRS.universal);
|
|
374
437
|
var AGENT_OPTIONS = [
|
|
375
438
|
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (the only directory Claude Code reads)" },
|
|
376
439
|
{
|
|
@@ -437,15 +500,59 @@ function disposition(path, existing, version, force) {
|
|
|
437
500
|
stale: true
|
|
438
501
|
};
|
|
439
502
|
}
|
|
503
|
+
var untouched = (path) => ({
|
|
504
|
+
kind: "kept",
|
|
505
|
+
note: `${path} already exists and was not generated by nola \u2014 left untouched`,
|
|
506
|
+
stale: false
|
|
507
|
+
});
|
|
508
|
+
async function linkClaudeDir(root, version, force) {
|
|
509
|
+
const base = SKILL_DIRS.claude;
|
|
510
|
+
const link = join5(root, ...base.split("/"));
|
|
511
|
+
const existing = await lstat(link).catch(() => void 0);
|
|
512
|
+
if (existing?.isSymbolicLink()) {
|
|
513
|
+
const target = join5(root, ...SKILL_DIRS.universal.split("/"));
|
|
514
|
+
const [resolved, wanted] = await Promise.all([
|
|
515
|
+
realpath(link).catch(() => void 0),
|
|
516
|
+
realpath(target).catch(() => void 0)
|
|
517
|
+
]);
|
|
518
|
+
if (resolved !== void 0 && resolved === wanted) {
|
|
519
|
+
return { kind: "kept", note: `${base} is a link to ${SKILL_DIRS.universal} \u2014 up to date`, stale: false };
|
|
520
|
+
}
|
|
521
|
+
if (resolved !== void 0) return untouched(base);
|
|
522
|
+
await rm3(link);
|
|
523
|
+
} else if (existing?.isDirectory()) {
|
|
524
|
+
const marker = join5(link, "SKILL.md");
|
|
525
|
+
const found = existsSync5(marker) ? STAMP_RE.exec(await readFile5(marker, "utf8")) : null;
|
|
526
|
+
if (!found) return untouched(base);
|
|
527
|
+
if (!force) {
|
|
528
|
+
const note = found[1] === version ? `${base} is a copy (v${version}) \u2014 re-run with --force to replace it with a link to ${SKILL_DIRS.universal}` : `${base}/SKILL.md is stale (v${found[1]} \u2192 v${version}) \u2014 re-run with --force to replace it`;
|
|
529
|
+
return { kind: "kept", note, stale: true };
|
|
530
|
+
}
|
|
531
|
+
await rm3(link, { recursive: true });
|
|
532
|
+
} else if (existing?.isFile()) {
|
|
533
|
+
if ((await readFile5(link, "utf8")).trim() !== CLAUDE_LINK_TARGET) return untouched(base);
|
|
534
|
+
await rm3(link);
|
|
535
|
+
} else if (existing) {
|
|
536
|
+
return untouched(base);
|
|
537
|
+
}
|
|
538
|
+
await mkdir3(dirname2(link), { recursive: true });
|
|
539
|
+
try {
|
|
540
|
+
await symlink2(CLAUDE_LINK_TARGET, link, "dir");
|
|
541
|
+
} catch (err) {
|
|
542
|
+
const code = err.code ?? "error";
|
|
543
|
+
return { kind: "unlinkable", note: `could not link ${base} \u2192 ${SKILL_DIRS.universal} (${code}) \u2014 wrote a copy instead` };
|
|
544
|
+
}
|
|
545
|
+
return { kind: "linked" };
|
|
546
|
+
}
|
|
440
547
|
async function writeSkillDir(root, base, s, version) {
|
|
441
548
|
const dir = join5(root, ...base.split("/"));
|
|
442
549
|
await mkdir3(join5(dir, "references"), { recursive: true });
|
|
443
550
|
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}
|
|
444
551
|
`);
|
|
445
|
-
await
|
|
552
|
+
await writeFile4(join5(dir, "SKILL.md"), stamped);
|
|
446
553
|
const wrote = [`${base}/SKILL.md`];
|
|
447
554
|
for (const name of (await readdir3(join5(SKILLS_DIR, "references"))).sort()) {
|
|
448
|
-
await
|
|
555
|
+
await writeFile4(join5(dir, "references", name), await readFile5(join5(SKILLS_DIR, "references", name), "utf8"));
|
|
449
556
|
wrote.push(`${base}/references/${name}`);
|
|
450
557
|
}
|
|
451
558
|
return wrote;
|
|
@@ -459,19 +566,35 @@ async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
|
459
566
|
const removed = [];
|
|
460
567
|
const skipped = [];
|
|
461
568
|
let stale = false;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
const marker = join5(root, ...base.split("/"), "SKILL.md");
|
|
569
|
+
const copy = async (base) => {
|
|
570
|
+
const dir = join5(root, ...base.split("/"));
|
|
571
|
+
const marker = join5(dir, "SKILL.md");
|
|
466
572
|
if (existsSync5(marker)) {
|
|
467
573
|
const d = disposition(`${base}/SKILL.md`, await readFile5(marker, "utf8"), version, force);
|
|
468
574
|
if (!d.write) {
|
|
469
575
|
skipped.push(d.note);
|
|
470
576
|
stale ||= d.stale;
|
|
471
|
-
|
|
577
|
+
return;
|
|
472
578
|
}
|
|
579
|
+
} else if ((await lstat(dir).catch(() => void 0))?.isSymbolicLink()) {
|
|
580
|
+
await rm3(dir);
|
|
473
581
|
}
|
|
474
582
|
wrote.push(...await writeSkillDir(root, base, source, version));
|
|
583
|
+
};
|
|
584
|
+
if (agents.includes("universal")) await copy(SKILL_DIRS.universal);
|
|
585
|
+
if (agents.includes("claude")) {
|
|
586
|
+
if (!agents.includes("universal")) {
|
|
587
|
+
await copy(SKILL_DIRS.claude);
|
|
588
|
+
} else {
|
|
589
|
+
const link = await linkClaudeDir(root, version, force);
|
|
590
|
+
if (link.kind === "linked") {
|
|
591
|
+
wrote.push(`${SKILL_DIRS.claude} \u2192 ${SKILL_DIRS.universal}`);
|
|
592
|
+
} else {
|
|
593
|
+
skipped.push(link.note);
|
|
594
|
+
if (link.kind === "kept") stale ||= link.stale;
|
|
595
|
+
else await copy(SKILL_DIRS.claude);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
475
598
|
}
|
|
476
599
|
if (agents.includes("universal")) {
|
|
477
600
|
for (const path of LEGACY_ADAPTERS) {
|
|
@@ -496,7 +619,7 @@ ${agentsSection(source, version)}`
|
|
|
496
619
|
);
|
|
497
620
|
} else {
|
|
498
621
|
await mkdir3(dirname2(abs), { recursive: true });
|
|
499
|
-
await
|
|
622
|
+
await writeFile4(abs, agentsMd(source, version));
|
|
500
623
|
wrote.push("AGENTS.md");
|
|
501
624
|
}
|
|
502
625
|
}
|
|
@@ -631,7 +754,7 @@ function sessionIdOf(url) {
|
|
|
631
754
|
}
|
|
632
755
|
|
|
633
756
|
// src/credentials.ts
|
|
634
|
-
import { mkdir as mkdir4, readFile as readFile6, writeFile as
|
|
757
|
+
import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
635
758
|
import { homedir } from "node:os";
|
|
636
759
|
import { join as join6 } from "node:path";
|
|
637
760
|
var CREDENTIALS_FILE = "credentials.json";
|
|
@@ -681,7 +804,7 @@ async function rewrite(home, mutate) {
|
|
|
681
804
|
const next = { ...raw, version: CREDENTIALS_VERSION, sessions };
|
|
682
805
|
try {
|
|
683
806
|
await mkdir4(join6(home, ".nola"), { recursive: true });
|
|
684
|
-
await
|
|
807
|
+
await writeFile5(credentialsPath(home), `${JSON.stringify(next, null, 2)}
|
|
685
808
|
`, { mode: 384 });
|
|
686
809
|
return "written";
|
|
687
810
|
} catch {
|
|
@@ -703,7 +826,7 @@ async function deleteSession(home, apiUrl) {
|
|
|
703
826
|
}
|
|
704
827
|
|
|
705
828
|
// src/home-config.ts
|
|
706
|
-
import { mkdir as mkdir5, readFile as readFile7, writeFile as
|
|
829
|
+
import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile6 } from "node:fs/promises";
|
|
707
830
|
import { homedir as homedir2 } from "node:os";
|
|
708
831
|
import { join as join7 } from "node:path";
|
|
709
832
|
var HOME_CONFIG_FILE = "config.json";
|
|
@@ -755,7 +878,7 @@ async function recordTrialAccount(home, apiUrl, record) {
|
|
|
755
878
|
const next = { ...raw, version: HOME_CONFIG_VERSION, accounts };
|
|
756
879
|
try {
|
|
757
880
|
await mkdir5(join7(dir, ".nola"), { recursive: true });
|
|
758
|
-
await
|
|
881
|
+
await writeFile6(homeConfigPath(dir), `${JSON.stringify(next, null, 2)}
|
|
759
882
|
`);
|
|
760
883
|
return "written";
|
|
761
884
|
} catch {
|
|
@@ -1187,19 +1310,18 @@ ${renderCommandHelp(cmd, meta.name, p)}`);
|
|
|
1187
1310
|
|
|
1188
1311
|
// src/ide.ts
|
|
1189
1312
|
import { existsSync as existsSync6 } from "node:fs";
|
|
1190
|
-
import { mkdir as mkdir6, writeFile as
|
|
1313
|
+
import { mkdir as mkdir6, writeFile as writeFile7 } from "node:fs/promises";
|
|
1191
1314
|
import { join as join8, resolve as resolve5 } from "node:path";
|
|
1192
|
-
var
|
|
1315
|
+
var launchJson = (entry) => ({
|
|
1193
1316
|
version: "0.2.0",
|
|
1194
1317
|
configurations: [
|
|
1195
1318
|
{
|
|
1196
1319
|
type: "node",
|
|
1197
1320
|
request: "launch",
|
|
1198
1321
|
name: "Nola: Launch main",
|
|
1199
|
-
// ${workspaceFolder}
|
|
1200
|
-
//
|
|
1201
|
-
|
|
1202
|
-
program: "${workspaceFolder}/src/main.ts",
|
|
1322
|
+
// ${workspaceFolder} is VS Code's own launch-config variable — it must
|
|
1323
|
+
// reach the file verbatim, hence the escaped dollar.
|
|
1324
|
+
program: `\${workspaceFolder}/${entry}`,
|
|
1203
1325
|
runtimeArgs: ["--import", "nola-lang/register", "--enable-source-maps"],
|
|
1204
1326
|
console: "integratedTerminal",
|
|
1205
1327
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: VS Code variable syntax
|
|
@@ -1209,15 +1331,15 @@ var LAUNCH_JSON = {
|
|
|
1209
1331
|
skipFiles: ["<node_internals>/**", "**/node_modules/**"]
|
|
1210
1332
|
}
|
|
1211
1333
|
]
|
|
1212
|
-
};
|
|
1334
|
+
});
|
|
1213
1335
|
var EXTENSIONS_JSON = { recommendations: ["nola.nola-vscode"] };
|
|
1214
|
-
async function writeVscodeSetup(targetDir) {
|
|
1336
|
+
async function writeVscodeSetup(targetDir, entry = DEFAULT_ENTRY) {
|
|
1215
1337
|
const vscodeDir = join8(resolve5(targetDir), ".vscode");
|
|
1216
1338
|
await mkdir6(vscodeDir, { recursive: true });
|
|
1217
1339
|
const wrote = [];
|
|
1218
1340
|
const skipped = [];
|
|
1219
1341
|
const files = [
|
|
1220
|
-
["launch.json",
|
|
1342
|
+
["launch.json", launchJson(entry)],
|
|
1221
1343
|
["extensions.json", EXTENSIONS_JSON]
|
|
1222
1344
|
];
|
|
1223
1345
|
for (const [name, content] of files) {
|
|
@@ -1227,7 +1349,7 @@ async function writeVscodeSetup(targetDir) {
|
|
|
1227
1349
|
skipped.push(`${rel} already exists \u2014 left untouched`);
|
|
1228
1350
|
continue;
|
|
1229
1351
|
}
|
|
1230
|
-
await
|
|
1352
|
+
await writeFile7(path, `${JSON.stringify(content, null, 2)}
|
|
1231
1353
|
`);
|
|
1232
1354
|
wrote.push(rel);
|
|
1233
1355
|
}
|
|
@@ -1236,7 +1358,7 @@ async function writeVscodeSetup(targetDir) {
|
|
|
1236
1358
|
|
|
1237
1359
|
// src/trial.ts
|
|
1238
1360
|
import { existsSync as existsSync7 } from "node:fs";
|
|
1239
|
-
import { appendFile, readFile as readFile8, writeFile as
|
|
1361
|
+
import { appendFile, readFile as readFile8, writeFile as writeFile8 } from "node:fs/promises";
|
|
1240
1362
|
import { join as join9, resolve as resolve6 } from "node:path";
|
|
1241
1363
|
var TRIAL_CONFIG_URL = new URL("../templates/_providers/nola.config.ts", import.meta.url);
|
|
1242
1364
|
var ENV_KEY = "NOLA_API_KEY";
|
|
@@ -1263,7 +1385,7 @@ async function writeEnvKey(dir, apiKey, opts = {}) {
|
|
|
1263
1385
|
const current = await readFile8(envPath, "utf8");
|
|
1264
1386
|
if (ENV_KEY_LINE.test(current)) {
|
|
1265
1387
|
if (opts.replace) {
|
|
1266
|
-
await
|
|
1388
|
+
await writeFile8(envPath, current.replace(new RegExp(`^(\\s*(?:export\\s+)?${ENV_KEY}\\s*=).*$`, "gm"), `$1${apiKey}`));
|
|
1267
1389
|
wrote.push(".env");
|
|
1268
1390
|
} else {
|
|
1269
1391
|
skipped.push(`.env already sets ${ENV_KEY} \u2014 left untouched; the new trial key was not written`);
|
|
@@ -1275,7 +1397,7 @@ async function writeEnvKey(dir, apiKey, opts = {}) {
|
|
|
1275
1397
|
wrote.push(".env");
|
|
1276
1398
|
}
|
|
1277
1399
|
} else {
|
|
1278
|
-
await
|
|
1400
|
+
await writeFile8(envPath, `${ENV_KEY}=${apiKey}
|
|
1279
1401
|
`);
|
|
1280
1402
|
wrote.push(".env");
|
|
1281
1403
|
}
|
|
@@ -1288,7 +1410,7 @@ async function ensureEnvIgnored(dir) {
|
|
|
1288
1410
|
const missing = IGNORE_LINES.filter((line) => !present.has(line));
|
|
1289
1411
|
if (missing.length === 0) return [];
|
|
1290
1412
|
const lead = existing === "" ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
1291
|
-
await
|
|
1413
|
+
await writeFile8(ignorePath, `${existing}${lead}${missing.join("\n")}
|
|
1292
1414
|
`);
|
|
1293
1415
|
return [".gitignore"];
|
|
1294
1416
|
}
|
|
@@ -1299,7 +1421,7 @@ async function applyTrial(dir, init) {
|
|
|
1299
1421
|
if (init.hasConfig) {
|
|
1300
1422
|
skipped.push('nola.config.ts already exists \u2014 set `model: "nola"` to use the trial key');
|
|
1301
1423
|
} else {
|
|
1302
|
-
await
|
|
1424
|
+
await writeFile8(join9(root, "nola.config.ts"), await readFile8(TRIAL_CONFIG_URL, "utf8"));
|
|
1303
1425
|
wrote.push("nola.config.ts");
|
|
1304
1426
|
}
|
|
1305
1427
|
return { wrote, skipped };
|
|
@@ -1411,6 +1533,7 @@ function installArgs(pm) {
|
|
|
1411
1533
|
return pm === "npm" ? ["install", "--no-audit", "--no-fund"] : ["install"];
|
|
1412
1534
|
}
|
|
1413
1535
|
var realLauncher = {
|
|
1536
|
+
hasVscode: () => findOnPath("code") !== void 0,
|
|
1414
1537
|
install: (pm, dir, onOutput) => run(pm, installArgs(pm), dir, onOutput),
|
|
1415
1538
|
async openVscode(dir, entry) {
|
|
1416
1539
|
const code = findOnPath("code");
|
|
@@ -1472,8 +1595,10 @@ function packageManagerCommands(pm) {
|
|
|
1472
1595
|
|
|
1473
1596
|
// src/flow.ts
|
|
1474
1597
|
var DEFAULT_DIR = "nola-app";
|
|
1475
|
-
var DEFAULT_TEMPLATE = "
|
|
1598
|
+
var DEFAULT_TEMPLATE = "feature-extraction";
|
|
1476
1599
|
var NAME_QUESTION = `Project name (press Enter to keep "${DEFAULT_DIR}"):`;
|
|
1600
|
+
var SETUP_STEP_ASKED = false;
|
|
1601
|
+
var DEFAULT_IDE = "vscode";
|
|
1477
1602
|
var SETUP_QUESTION = "Set up your Editor and Coding Agents?";
|
|
1478
1603
|
var EDITOR_QUESTION = "Set up your Editor?";
|
|
1479
1604
|
var AGENTS_QUESTION = "Set up Coding Agents?";
|
|
@@ -1483,13 +1608,36 @@ var EDITOR_OPTIONS = [
|
|
|
1483
1608
|
];
|
|
1484
1609
|
function templateOption(t) {
|
|
1485
1610
|
const vendor = t.provider ? ` (${t.provider.label})` : "";
|
|
1486
|
-
return { value: t.name, label:
|
|
1611
|
+
return { value: t.name, label: `${t.name}${vendor}`, hint: t.label };
|
|
1612
|
+
}
|
|
1613
|
+
var TEMPLATE_QUESTION = "Select a template:";
|
|
1614
|
+
var EXAMPLE_QUESTION = "Select an example:";
|
|
1615
|
+
var MORE_EXAMPLES = "more-examples";
|
|
1616
|
+
var BACK = "back";
|
|
1617
|
+
var builtinNames = () => TEMPLATES.filter((t) => t.source === "builtin").map((t) => `"${t.name}"`).join(", ");
|
|
1618
|
+
function templateMenu() {
|
|
1619
|
+
const featured = featuredNames().map((name) => templateOption(templateByName(name)));
|
|
1620
|
+
return [...featured, { value: MORE_EXAMPLES, label: "More examples\u2026", hint: exampleNames().join(", ") }];
|
|
1621
|
+
}
|
|
1622
|
+
function exampleMenu() {
|
|
1623
|
+
const examples = exampleNames().map((name) => templateOption(templateByName(name)));
|
|
1624
|
+
return [...examples, { value: BACK, label: "\u2190 Back", hint: "the builtin templates" }];
|
|
1625
|
+
}
|
|
1626
|
+
async function selectTemplate(prompter) {
|
|
1627
|
+
for (; ; ) {
|
|
1628
|
+
const choice = await prompter.select(TEMPLATE_QUESTION, templateMenu());
|
|
1629
|
+
if (choice === null) return null;
|
|
1630
|
+
if (choice !== MORE_EXAMPLES) return choice;
|
|
1631
|
+
const example = await prompter.select(EXAMPLE_QUESTION, exampleMenu());
|
|
1632
|
+
if (example === null) return null;
|
|
1633
|
+
if (example !== BACK) return example;
|
|
1634
|
+
}
|
|
1487
1635
|
}
|
|
1488
1636
|
async function resolveSetup(input, prompter, dir) {
|
|
1489
1637
|
const ide = input.ide === "vscode" || input.ide === "none" ? input.ide : void 0;
|
|
1490
1638
|
const agents = input.agents !== void 0 ? parseAgentsFlag(input.agents) : void 0;
|
|
1491
1639
|
if (ide !== void 0 && agents !== void 0) return { ide, agents };
|
|
1492
|
-
if (!input.interactive) return { ide: ide ??
|
|
1640
|
+
if (!input.interactive || !SETUP_STEP_ASKED) return { ide: ide ?? DEFAULT_IDE, agents: agents ?? defaultAgents(dir) };
|
|
1493
1641
|
const groups = [];
|
|
1494
1642
|
const initial = [];
|
|
1495
1643
|
if (ide === void 0) {
|
|
@@ -1523,8 +1671,17 @@ function nolaHint(path) {
|
|
|
1523
1671
|
return providerById("nola")?.hint ?? "";
|
|
1524
1672
|
}
|
|
1525
1673
|
}
|
|
1526
|
-
function
|
|
1527
|
-
|
|
1674
|
+
function typesafeHint(base, template) {
|
|
1675
|
+
if (template === void 0) return `${base} (literal unions and booleans only)`;
|
|
1676
|
+
if (templateByName(template)?.provider?.label === "typesafe.ai") return base;
|
|
1677
|
+
return `${base} (does not support every construct in ${template}: literal unions and booleans only)`;
|
|
1678
|
+
}
|
|
1679
|
+
function providerOptions(path, template) {
|
|
1680
|
+
return PROVIDERS.map((p) => ({
|
|
1681
|
+
value: p.id,
|
|
1682
|
+
label: p.label,
|
|
1683
|
+
hint: p.id === "nola" ? nolaHint(path) : p.id === "typesafe" ? typesafeHint(p.hint, template) : p.hint
|
|
1684
|
+
}));
|
|
1528
1685
|
}
|
|
1529
1686
|
function flaggedProvider(input) {
|
|
1530
1687
|
const fromTrial = input.trial === void 0 ? void 0 : input.trial ? "nola" : "none";
|
|
@@ -1537,21 +1694,22 @@ function flaggedProvider(input) {
|
|
|
1537
1694
|
}
|
|
1538
1695
|
return input.provider;
|
|
1539
1696
|
}
|
|
1540
|
-
async function resolveProvider(input, prompter,
|
|
1697
|
+
async function resolveProvider(input, prompter, template) {
|
|
1541
1698
|
const flagged = flaggedProvider(input);
|
|
1542
1699
|
if (flagged !== void 0) return flagged;
|
|
1700
|
+
const pinned = template !== void 0 && templateByName(template)?.provider !== void 0;
|
|
1543
1701
|
if (pinned || !input.interactive) return "none";
|
|
1544
1702
|
const path = input.keyPath ?? { kind: "trial" };
|
|
1545
1703
|
while (true) {
|
|
1546
|
-
const choice = await prompter.select(PROVIDER_QUESTION, providerOptions(path));
|
|
1704
|
+
const choice = await prompter.select(PROVIDER_QUESTION, providerOptions(path, template));
|
|
1547
1705
|
if (choice === null) return null;
|
|
1548
1706
|
if (!isProviderId(choice)) throw new Error(`unexpected provider choice "${choice}"`);
|
|
1549
1707
|
if (choice === "nola" && path.kind === "sign-in" && input.signIn && !await input.signIn()) continue;
|
|
1550
1708
|
return choice;
|
|
1551
1709
|
}
|
|
1552
1710
|
}
|
|
1553
|
-
async function resolveExtras(input, prompter, dir,
|
|
1554
|
-
const provider = await resolveProvider(input, prompter,
|
|
1711
|
+
async function resolveExtras(input, prompter, dir, template) {
|
|
1712
|
+
const provider = await resolveProvider(input, prompter, template);
|
|
1555
1713
|
if (provider === null) return null;
|
|
1556
1714
|
const setup = await resolveSetup(input, prompter, dir);
|
|
1557
1715
|
return setup === null ? null : { provider, ...setup };
|
|
@@ -1643,12 +1801,12 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
1643
1801
|
if (!input.interactive) {
|
|
1644
1802
|
template = DEFAULT_TEMPLATE;
|
|
1645
1803
|
} else {
|
|
1646
|
-
const choice = await prompter
|
|
1804
|
+
const choice = await selectTemplate(prompter);
|
|
1647
1805
|
if (choice === null) return { kind: "cancelled" };
|
|
1648
1806
|
template = choice;
|
|
1649
1807
|
}
|
|
1650
1808
|
}
|
|
1651
|
-
const extras = await resolveExtras(input, prompter, dir,
|
|
1809
|
+
const extras = await resolveExtras(input, prompter, dir, template);
|
|
1652
1810
|
if (extras === null) return { kind: "cancelled" };
|
|
1653
1811
|
return { kind: "scaffold", dir, template, force, ...extras };
|
|
1654
1812
|
}
|
|
@@ -1672,10 +1830,16 @@ function plainPrompter() {
|
|
|
1672
1830
|
};
|
|
1673
1831
|
}
|
|
1674
1832
|
var FLOW_OPTIONS = {
|
|
1675
|
-
template: {
|
|
1833
|
+
template: {
|
|
1834
|
+
type: "string",
|
|
1835
|
+
description: "template to scaffold (feature-extraction, function-calling, typescript-interop, empty, or a curated example)"
|
|
1836
|
+
},
|
|
1676
1837
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
1677
|
-
ide: { type: "string", description: "editor setup: vscode | none" },
|
|
1678
|
-
agents: {
|
|
1838
|
+
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
1839
|
+
agents: {
|
|
1840
|
+
type: "string",
|
|
1841
|
+
description: `agent skill files: ${AGENT_IDS.join(",")} | all | none (default: claude,universal)`
|
|
1842
|
+
},
|
|
1679
1843
|
provider: {
|
|
1680
1844
|
type: "string",
|
|
1681
1845
|
description: `inference provider: ${providerIds().join(" | ")} (nola = 25 free runs; non-interactive default: none)`
|
|
@@ -1697,7 +1861,7 @@ function commented(command, note) {
|
|
|
1697
1861
|
}
|
|
1698
1862
|
function nextSteps(outcome, fileCount, name, pm, installed = false, grant = null) {
|
|
1699
1863
|
const cmd = packageManagerCommands(pm);
|
|
1700
|
-
const vendorEnv =
|
|
1864
|
+
const vendorEnv = vendorEnvVar(outcome.template, outcome.provider);
|
|
1701
1865
|
const startNote = grant ? grant.source === "trial" ? "25 free Nola runs \u2014 key in .env" : "key in .env (run `npx nola-lang account` to check the balance)" : vendorEnv ? `set ${vendorEnv} in .env first` : outcome.template === "empty" ? "set OPENAI_API_KEY first" : "runs offline \u2014 no API key needed";
|
|
1702
1866
|
const lines = [
|
|
1703
1867
|
...outcome.dir === "." ? [] : [`cd ${outcome.dir}`],
|
|
@@ -1711,7 +1875,6 @@ Next steps:
|
|
|
1711
1875
|
${lines.join("\n ")}`;
|
|
1712
1876
|
}
|
|
1713
1877
|
var INSTALL_LOG_TAIL = 20;
|
|
1714
|
-
var ENTRY_FILE = "src/main.ts";
|
|
1715
1878
|
function lastOutputLine(output) {
|
|
1716
1879
|
const lines = stripVTControlCharacters(output).split(/\r?\n/);
|
|
1717
1880
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -1720,8 +1883,11 @@ function lastOutputLine(output) {
|
|
|
1720
1883
|
}
|
|
1721
1884
|
return void 0;
|
|
1722
1885
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1886
|
+
var INSTALL_OPEN_QUESTION = "Install dependencies and open VS Code?";
|
|
1887
|
+
var INSTALL_QUESTION = "Install dependencies?";
|
|
1888
|
+
async function offerInstallAndOpen(dir, entryPath, pm, prompter, launcher, linkCheckout) {
|
|
1889
|
+
const vscode = launcher.hasVscode();
|
|
1890
|
+
const yes = await prompter.confirm(vscode ? INSTALL_OPEN_QUESTION : INSTALL_QUESTION, true);
|
|
1725
1891
|
if (!yes) return { installed: false };
|
|
1726
1892
|
const cmd = packageManagerCommands(pm);
|
|
1727
1893
|
const task = prompter.progress(`Installing dependencies (${cmd.install})`);
|
|
@@ -1736,8 +1902,10 @@ async function offerInstallAndOpen(dir, pm, prompter, launcher, linkCheckout) {
|
|
|
1736
1902
|
task.done(`Installed dependencies (${cmd.install})`);
|
|
1737
1903
|
if (linkCheckout !== null) {
|
|
1738
1904
|
const linked = await linkCheckoutPackages(dir, linkCheckout);
|
|
1905
|
+
const skipped = await skipCheckoutDistInLaunch(dir);
|
|
1739
1906
|
prompter.note(
|
|
1740
|
-
`${LINK_CHECKOUT_ENV}: linked ${linked.join(", ")} to ${join11(linkCheckout, "packages")} \u2014 a later ${cmd.install} restores the registry copies.`
|
|
1907
|
+
`${LINK_CHECKOUT_ENV}: linked ${linked.join(", ")} to ${join11(linkCheckout, "packages")} \u2014 a later ${cmd.install} restores the registry copies.` + (skipped ? `
|
|
1908
|
+
Added "${CHECKOUT_DIST_SKIP_GLOB}" to skipFiles in .vscode/launch.json so stepping stays in your code.` : "")
|
|
1741
1909
|
);
|
|
1742
1910
|
}
|
|
1743
1911
|
} else {
|
|
@@ -1746,7 +1914,8 @@ async function offerInstallAndOpen(dir, pm, prompter, launcher, linkCheckout) {
|
|
|
1746
1914
|
prompter.note(`${tail ? `${tail}
|
|
1747
1915
|
` : ""}Run ${cmd.install} yourself once the cause is fixed.`);
|
|
1748
1916
|
}
|
|
1749
|
-
|
|
1917
|
+
if (!vscode) return { installed };
|
|
1918
|
+
const entry = existsSync9(join11(dir, entryPath)) ? entryPath : void 0;
|
|
1750
1919
|
const opened = await launcher.openVscode(dir, entry);
|
|
1751
1920
|
if (opened === "not-found") {
|
|
1752
1921
|
prompter.note(
|
|
@@ -1876,7 +2045,7 @@ Next steps:
|
|
|
1876
2045
|
}
|
|
1877
2046
|
let ideWrote = [];
|
|
1878
2047
|
if (outcome.ide === "vscode") {
|
|
1879
|
-
const ide = await writeVscodeSetup(outcome.dir);
|
|
2048
|
+
const ide = await writeVscodeSetup(outcome.dir, entryFile(outcome.template));
|
|
1880
2049
|
ideWrote = ide.wrote;
|
|
1881
2050
|
for (const note of ide.skipped) prompter.note(note);
|
|
1882
2051
|
}
|
|
@@ -1886,7 +2055,7 @@ Next steps:
|
|
|
1886
2055
|
agWrote = ag.wrote;
|
|
1887
2056
|
for (const note of ag.skipped) prompter.note(note);
|
|
1888
2057
|
}
|
|
1889
|
-
const launch = interactive && outcome.ide !== "none" ? await offerInstallAndOpen(outcome.dir, pm, prompter, opts.launcher ?? realLauncher, opts.linkCheckout ?? linkCheckoutFromEnv()) : { installed: false };
|
|
2058
|
+
const launch = interactive && outcome.ide !== "none" ? await offerInstallAndOpen(outcome.dir, entryFile(outcome.template), pm, prompter, opts.launcher ?? realLauncher, opts.linkCheckout ?? linkCheckoutFromEnv()) : { installed: false };
|
|
1890
2059
|
const message = nextSteps(
|
|
1891
2060
|
{ ...outcome, provider },
|
|
1892
2061
|
files.length + trialWrote.length + ideWrote.length + agWrote.length,
|
|
@@ -1901,7 +2070,7 @@ Next steps:
|
|
|
1901
2070
|
} catch (err) {
|
|
1902
2071
|
if (err instanceof ExampleFetchError && interactive) {
|
|
1903
2072
|
prompter.note(`${err.message}
|
|
1904
|
-
The
|
|
2073
|
+
The builtin templates (${builtinNames()}) work offline.`);
|
|
1905
2074
|
input.dir = outcome.dir;
|
|
1906
2075
|
input.template = void 0;
|
|
1907
2076
|
continue;
|
|
@@ -1997,5 +2166,7 @@ export {
|
|
|
1997
2166
|
plainPrompter,
|
|
1998
2167
|
FLOW_OPTIONS,
|
|
1999
2168
|
CREATE_COMMAND,
|
|
2169
|
+
INSTALL_OPEN_QUESTION,
|
|
2170
|
+
INSTALL_QUESTION,
|
|
2000
2171
|
runFlow
|
|
2001
2172
|
};
|