@nimblescape/typescript 0.2.0 → 0.2.1

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 CHANGED
@@ -6,7 +6,7 @@ Node.js, in one step: `ts main.ts`. A program can use `prompt()`, `alert()`
6
6
  and `confirm()` as in the browser, without an import. A folder needs no
7
7
  `tsconfig.json` and no `package.json`.
8
8
 
9
- ![VS Code with a terminal, in six scenes: npm installs @nimblescape/typescript globally; ts runs main.ts, and ts main.ts 3 gives the program the argument 3; ts broken.ts stops at a type error, and ts --check checks main.ts; ts --init and ts --init --browser write a tsconfig.json; ts --install-typescript installs the TypeScript of the newest VS Code, and ts --version prints the versions; ts --completion bash turns on the Tab completion of the options, and ts --help prints the help; a blue band above the window names each scene, and a label at the bottom names a pressed key and what it does](https://raw.githubusercontent.com/nimblescape/nimblescape-typescript/main/docs/images/ts.gif)
9
+ ![VS Code with a terminal, in six scenes: npm installs @nimblescape/typescript globally; ts runs main.ts, and ts main.ts 3 gives the program the argument 3; ts broken.ts stops at a type error, and ts --check checks main.ts; ts --init writes the tsconfig.json of a project of ts, with the entry of the plugin, and installs the types of Node.js after a question; ts --install-typescript installs the TypeScript of the newest VS Code, and ts --version prints the versions; ts --completion bash turns on the Tab completion of the options, and ts --help prints the help; a blue band above the window names each scene, and a label at the bottom names a pressed key and what it does](https://raw.githubusercontent.com/nimblescape/nimblescape-typescript/main/docs/images/ts.gif)
10
10
 
11
11
  `ts` is a small wrapper around `tsc` and `node`. Every error comes from
12
12
  `tsc` or from Node.js, and the only addition to Node.js is the three
@@ -37,14 +37,14 @@ comes first in these terminals.
37
37
  ts # runs main.ts of this folder
38
38
  ts dice.ts 3 6 # runs dice.ts with the arguments 3 and 6
39
39
  ts --check main.ts # checks main.ts and runs nothing
40
+ ts main.js # runs a JavaScript file in the same way
40
41
  ```
41
42
 
42
43
  | Command | Effect |
43
44
  | --- | --- |
44
45
  | `ts [file] [arguments]` | Checks the file with `tsc`, compiles it and runs it with Node.js. Without a file, `ts` runs `main.ts` of the current folder. The words after the file are the arguments of the program (`process.argv`). When `tsc` finds an error, `ts` prints it and runs nothing. |
45
46
  | `ts --check [file]` | Checks the file and runs nothing. Without a file, `ts` checks `main.ts` of the current folder, and without `main.ts` the project of the `tsconfig.json` of the current folder. |
46
- | `ts --init` | Writes a `tsconfig.json` into the current folder: the file of `tsc --init`, with the settings for Node.js (`lib` `esnext` and the types of Node.js) and without `noUncheckedIndexedAccess`. It does not replace an existing `tsconfig.json`. |
47
- | `ts --init --browser` | Writes a `tsconfig.json` for code that runs in the browser: `lib` `esnext` and `dom`, and no types of Node.js. |
47
+ | `ts --init` | Writes a `tsconfig.json` into the current folder: the file of `tsc --init`, with the settings for Node.js (`lib` `esnext`, the types of Node.js and `rewriteRelativeImportExtensions`), with the entry `"plugins": [{ "name": "nimblescape-typescript-plugin" }]`, which marks a project of `ts` (see [Projects with a tsconfig.json](#projects-with-a-tsconfigjson)), and without `noUncheckedIndexedAccess`. With TypeScript 7, it also writes a `package.json` with `"type": "module"` when the folder and the folders above have none (see [Limits](#limits)); with TypeScript 6, `ts` and the editor need none. When the folder cannot find the types of Node.js, `ts` asks to run `npm install --save-dev @types/node`. It replaces no existing file. |
48
48
  | `ts --install-typescript` | Installs the TypeScript of the newest VS Code globally with `npm install --global typescript@<version>`, without a question. |
49
49
  | `ts --completion <shell>` | Prints the Tab completion script for `bash`, `zsh` or `fish` (see [Tab completion](#tab-completion)). |
50
50
  | `ts --version`, `ts -v` | Prints the versions of `ts`, of the TypeScript that `ts` uses and of Node.js. |
@@ -53,6 +53,11 @@ ts --check main.ts # checks main.ts and runs nothing
53
53
  The options stand before the file; every word after the file goes to the
54
54
  program, also a word that starts with `-`. `--` ends the options.
55
55
 
56
+ A JavaScript file (`.js`, `.mjs` or `.cjs`) runs in the same way, also
57
+ when a `tsconfig.json` does not allow JavaScript. `tsc` checks its syntax,
58
+ and its types only when the file starts with the comment `// @ts-check`,
59
+ as in Deno. A TypeScript file may import a JavaScript file.
60
+
56
61
  `ts` ends with the exit code of the program, or with the exit code of `tsc`
57
62
  after an error of the check. An error of the program names the line of the
58
63
  `.ts` file, because Node.js runs with the source maps of `tsc`.
@@ -69,9 +74,17 @@ after an error of the check. An error of the program names the line of the
69
74
 
70
75
  They read from the keyboard and wait, so the program stops until you press
71
76
  Enter. Input from a file or a pipe works too, for example
72
- `ts main.ts < input.txt`. Without a `tsconfig.json`, `tsc` knows their types.
73
- A project with its own `tsconfig.json` declares them itself, for example in
74
- a file `globals.d.ts`:
77
+ `ts main.ts < input.txt`.
78
+
79
+ `ts` knows their types in every project of `ts`, as Deno knows its own
80
+ globals: the types are part of `ts`, and a project needs no file for them.
81
+ In a project of `ts --init`, `ts` adds them to the settings of its
82
+ `tsconfig.json`, unless the project has the types of the browser (`"lib"`
83
+ with `"dom"`), which declare the three functions themselves. The VS Code extension
84
+ [nimblescape TypeScript](https://marketplace.visualstudio.com/items?itemName=nimblescape.nimblescape-typescript)
85
+ does the same in the editor. Plain `tsc` does not know them; a project that
86
+ also has to pass plain `tsc` declares them in a file of its own, for example
87
+ `globals.d.ts`:
75
88
 
76
89
  ```ts
77
90
  declare function prompt(message?: string, defaultValue?: string): string | null;
@@ -91,12 +104,31 @@ wins. Without one, `ts` uses the built-in defaults of `tsc` (for example
91
104
  | `lib` | `["esnext"]` | JavaScript without the browser: `document` and `window` are errors. |
92
105
  | `types` | `["node"]` and the types of `prompt()`, `alert()` and `confirm()` | `process`, `node:fs` and the other modules of Node.js. `ts` contains the types of Node.js. |
93
106
  | `skipLibCheck` | `true` | Checks your files, not the type files of the packages, as `tsc --init` sets it. |
107
+ | `allowJs` | `true` | `ts` runs a JavaScript file, and a TypeScript file may import one. `checkJs` stays off, as in `tsc` and Deno. |
94
108
  | `rewriteRelativeImportExtensions` | `true` | An import names the `.ts` file, as Node.js needs it: `import { Dog } from "./dog.ts"`. |
95
109
 
96
110
  Without a `package.json`, every file is an ES module, as if the folder had a
97
- `package.json` with `"type": "module"`. With a `package.json`, its `type`
111
+ `package.json` with `"type": "module"`, as in Deno. This also holds for a
112
+ project of `ts --init`, in `ts` and in the editor; plain `tsc` reads such a
113
+ file as CommonJS for `module` `nodenext`. With a `package.json`, its `type`
98
114
  decides, as in Node.js.
99
115
 
116
+ ## Projects with a tsconfig.json
117
+
118
+ A `tsconfig.json` belongs to `ts` only when its `compilerOptions.plugins`
119
+ names the editor plugin, as `ts --init` writes it:
120
+
121
+ ```json
122
+ "plugins": [{ "name": "nimblescape-typescript-plugin" }]
123
+ ```
124
+
125
+ Only such a project gets the types of `prompt()`, `alert()` and `confirm()`,
126
+ and every file of it is an ES module without a `package.json`. `tsc` and
127
+ Vite ignore the entry. Any other `tsconfig.json`, for example the one of a
128
+ Vite project, is a standard project: `ts` checks it as `tsc` does, and the
129
+ editor plugin leaves it to TypeScript. Delete the line, and the project of
130
+ `ts --init` becomes a standard project.
131
+
100
132
  ## Which TypeScript
101
133
 
102
134
  `ts` contains no TypeScript. It uses the first of these:
@@ -149,11 +181,14 @@ bash-completion, and for zsh when `~/.zshrc` runs `compinit`.
149
181
  - TypeScript 7 cannot read the `package.json` that `ts` assumes: without a
150
182
  `package.json`, `ts` checks a file there with `module` `preserve` and
151
183
  `moduleResolution` `bundler`. The program runs the same.
152
- - With a `tsconfig.json`, TypeScript 7 does not add the declaration files of
153
- the project (`.d.ts`) to the check of one file; TypeScript 6 does.
154
- - After `ts --init`, install the types of Node.js in the project:
155
- `npm install --save-dev @types/node`.
156
184
  - `ts` runs no JSX and builds no libraries.
185
+ - Plain `tsc` does not know `prompt()`, `alert()` and `confirm()`; see
186
+ [the functions prompt, alert and confirm](#the-functions-prompt-alert-and-confirm).
187
+ It also does not make every file an ES module without a `package.json`.
188
+ - TypeScript 7 cannot read the `package.json` that `ts` assumes in a
189
+ project with a `tsconfig.json` either: there, the project needs a real
190
+ `package.json` with `"type": "module"`, which `ts --init` writes with
191
+ TypeScript 7.
157
192
 
158
193
  ---
159
194
 
package/dist/ts.js CHANGED
@@ -33,7 +33,6 @@ var SHELLS = ["bash", "zsh", "fish"];
33
33
  var OPTIONS = [
34
34
  { name: "--check", description: "check the types only and run nothing" },
35
35
  { name: "--init", description: "write a tsconfig.json with the settings of tsc --init for Node.js" },
36
- { name: "--browser", description: "with --init, the settings for code that runs in the browser" },
37
36
  { name: "--install-typescript", description: "install the TypeScript of the newest VS Code globally, without a question" },
38
37
  { name: "--completion", argument: "<shell>", values: SHELLS, description: "print the Tab completion script for bash, zsh or fish" },
39
38
  { name: "--version", short: "-v", description: "print the versions of ts, TypeScript and Node.js" },
@@ -43,7 +42,7 @@ function helpText(version) {
43
42
  const usage = [
44
43
  ["ts [file] [arguments]", "check, compile and run the file with Node.js (default main.ts)"],
45
44
  ["ts --check [file]", "check the file, or the project of the tsconfig.json of this folder"],
46
- ["ts --init [--browser]", "write a tsconfig.json into this folder"],
45
+ ["ts --init", "write a tsconfig.json into this folder"],
47
46
  ["ts --install-typescript", "install TypeScript globally with npm"],
48
47
  ["ts --completion <shell>", "print the Tab completion script"]
49
48
  ];
@@ -56,7 +55,7 @@ function helpText(version) {
56
55
  return rows.map(([left, right]) => ` ${left.padEnd(width)} ${right}`).join("\n");
57
56
  };
58
57
  return [
59
- `ts ${version}: checks a TypeScript file with tsc, compiles it and runs it with Node.js.`,
58
+ `ts ${version}: checks a TypeScript or JavaScript file with tsc, compiles it and runs it with Node.js.`,
60
59
  "",
61
60
  "Usage:",
62
61
  column(usage),
@@ -72,7 +71,7 @@ function helpText(version) {
72
71
 
73
72
  // src/cli/args.ts
74
73
  function parseArguments(argv) {
75
- const command = { check: false, init: false, browser: false, installTypeScript: false, version: false, help: false, args: [] };
74
+ const command = { check: false, init: false, installTypeScript: false, version: false, help: false, args: [] };
76
75
  const known = new Set(OPTIONS.flatMap((option) => [option.name, option.short].filter((name) => name !== void 0)));
77
76
  for (let i = 0; i < argv.length; i++) {
78
77
  const word = argv[i];
@@ -89,7 +88,6 @@ function parseArguments(argv) {
89
88
  if (!known.has(word)) return `Unknown option ${word}. See ts --help.`;
90
89
  if (word === "--check") command.check = true;
91
90
  else if (word === "--init") command.init = true;
92
- else if (word === "--browser") command.browser = true;
93
91
  else if (word === "--install-typescript") command.installTypeScript = true;
94
92
  else if (word === "--version" || word === "-v") command.version = true;
95
93
  else if (word === "--help" || word === "-h") command.help = true;
@@ -98,7 +96,6 @@ function parseArguments(argv) {
98
96
  if (!SHELLS.includes(command.completion)) return `--completion needs one of: ${SHELLS.join(", ")}.`;
99
97
  }
100
98
  }
101
- if (command.browser && !command.init) return "--browser works only with --init.";
102
99
  return command;
103
100
  }
104
101
 
@@ -109,15 +106,27 @@ var path3 = __toESM(require("node:path"));
109
106
 
110
107
  // src/defaults.ts
111
108
  var path = __toESM(require("node:path"));
109
+ var PLUGIN_NAME = "nimblescape-typescript-plugin";
110
+ function isTsProject(plugins) {
111
+ return Array.isArray(plugins) && plugins.some((entry) => typeof entry === "object" && entry !== null && entry.name === PLUGIN_NAME);
112
+ }
112
113
  var GLOBALS_TYPES = "nimblescape-globals";
113
114
  var DEFAULT_COMPILER_OPTIONS = {
114
115
  module: "nodenext",
115
116
  lib: ["esnext"],
116
117
  types: ["node", GLOBALS_TYPES],
117
118
  skipLibCheck: true,
119
+ // ts runs a JavaScript file too, and a TypeScript file may import one.
120
+ allowJs: true,
118
121
  // A relative import may end with .ts, as Node.js requires for a .ts file; tsc writes .js into the output.
119
122
  rewriteRelativeImportExtensions: true
120
123
  };
124
+ function hasBrowserTypes(lib) {
125
+ return lib === void 0 || lib.some((name) => /^(lib\.)?dom(\.|$)/i.test(name));
126
+ }
127
+ function isJavaScriptFile(file) {
128
+ return /\.[cm]?jsx?$/i.test(file);
129
+ }
121
130
  var VIRTUAL_PACKAGE_JSON = '{"type":"module"}';
122
131
  function rootPackageJson(file) {
123
132
  return path.join(path.parse(path.resolve(file)).root, "package.json");
@@ -243,16 +252,23 @@ function packageRoot(environment = process.env) {
243
252
  return environment[TYPES_VARIABLE] || path3.resolve(__dirname, "..");
244
253
  }
245
254
  function typeRoots(folder, environment = process.env) {
255
+ const roots = projectTypeRoots(folder);
256
+ const own = packageRoot(environment);
257
+ roots.push(path3.join(own, "types"));
258
+ const node = packageFolder("@types/node", own);
259
+ if (node !== void 0 && !roots.includes(path3.dirname(node))) roots.push(path3.dirname(node));
260
+ return roots;
261
+ }
262
+ function globalsFile(environment = process.env) {
263
+ return path3.join(packageRoot(environment), "types", GLOBALS_TYPES, "index.d.ts");
264
+ }
265
+ function projectTypeRoots(folder) {
246
266
  const roots = [];
247
267
  for (let current = path3.resolve(folder); ; current = path3.dirname(current)) {
248
268
  const types = path3.join(current, "node_modules", "@types");
249
269
  if (fs2.existsSync(types)) roots.push(types);
250
270
  if (path3.dirname(current) === current) break;
251
271
  }
252
- const own = packageRoot(environment);
253
- roots.push(path3.join(own, "types"));
254
- const node = packageFolder("@types/node", own);
255
- if (node !== void 0 && !roots.includes(path3.dirname(node))) roots.push(path3.dirname(node));
256
272
  return roots;
257
273
  }
258
274
  function projectOfFile(file) {
@@ -260,7 +276,7 @@ function projectOfFile(file) {
260
276
  const folder = path3.dirname(absolute);
261
277
  const configFolder = findUp(folder, "tsconfig.json", fs2.existsSync);
262
278
  const root = findUp(folder, "package.json", fs2.existsSync);
263
- const type = root === void 0 ? "module" : readManifest(root)?.type;
279
+ const type = root !== void 0 ? readManifest(root)?.type : configFolder === void 0 ? "module" : void 0;
264
280
  return {
265
281
  file: absolute,
266
282
  config: configFolder === void 0 ? void 0 : path3.join(configFolder, "tsconfig.json"),
@@ -268,11 +284,42 @@ function projectOfFile(file) {
268
284
  moduleType: type === "module" || type === "commonjs" ? type : void 0
269
285
  };
270
286
  }
271
- function declarationFiles(typescript, config) {
272
- const read = typescript.readConfigFile(config, typescript.sys.readFile);
273
- if (read.error) return [];
274
- const parsed = typescript.parseJsonConfigFileContent(read.config, typescript.sys, path3.dirname(config), void 0, config);
275
- return parsed.fileNames.filter((name) => /\.d\.[cm]?ts$/.test(name));
287
+ function projectConfig(install2, typescript, config) {
288
+ const isDeclaration = (name) => /\.d\.[cm]?ts$/.test(name);
289
+ const strings = (value) => Array.isArray(value) ? value.filter((name) => typeof name === "string") : void 0;
290
+ const settings = (files, typeRoots2, lib, plugins) => ({
291
+ files,
292
+ declarations: files.filter(isDeclaration),
293
+ typeRoots: typeRoots2,
294
+ lib,
295
+ ts: isTsProject(plugins)
296
+ });
297
+ const broken = settings([], false, void 0, void 0);
298
+ if (typescript) {
299
+ const read = typescript.readConfigFile(config, typescript.sys.readFile);
300
+ if (read.error) return broken;
301
+ const parsed = typescript.parseJsonConfigFileContent(read.config, typescript.sys, path3.dirname(config), void 0, config);
302
+ return settings(parsed.fileNames, parsed.options.typeRoots !== void 0, parsed.options.lib, parsed.options.plugins);
303
+ }
304
+ const manifest = readManifest(install2.folder);
305
+ const bin = path3.join(install2.folder, manifest?.bin?.tsc ?? "bin/tsc");
306
+ const result = (0, import_node_child_process2.spawnSync)(process.execPath, [bin, "--showConfig", "--project", config], { encoding: "utf8" });
307
+ try {
308
+ const shown = JSON.parse(result.stdout);
309
+ const files = (strings(shown.files) ?? []).map((name) => path3.resolve(path3.dirname(config), name));
310
+ return settings(files, shown.compilerOptions?.typeRoots !== void 0, strings(shown.compilerOptions?.lib), shown.compilerOptions?.plugins);
311
+ } catch {
312
+ return broken;
313
+ }
314
+ }
315
+ function addedFiles(settings) {
316
+ return !settings.ts || hasBrowserTypes(settings.lib) ? [] : [globalsFile()];
317
+ }
318
+ function extendedTypeRoots(config, folder, settings) {
319
+ const own = projectTypeRoots(path3.dirname(config));
320
+ const found = projectTypeRoots(folder);
321
+ const missing = !settings.typeRoots && own.some((root) => !found.includes(root));
322
+ return missing ? { typeRoots: [.../* @__PURE__ */ new Set([...own, ...found])] } : {};
276
323
  }
277
324
  function loadTypeScript(install2) {
278
325
  if (install2.major >= 7) return void 0;
@@ -285,7 +332,10 @@ function outputFile(job) {
285
332
  const relative3 = path3.relative(path3.parse(file).root, file).replace(/\.([cm]?)tsx?$/, (_, kind) => `.${kind}js`);
286
333
  return path3.join(outFolder(job), relative3);
287
334
  }
288
- function temporaryConfig(job, typescript) {
335
+ function jobSettings(job, typescript) {
336
+ return job.project.config === void 0 ? void 0 : projectConfig(job.typescript, typescript, job.project.config);
337
+ }
338
+ function temporaryConfig(job, typescript, settings = jobSettings(job, typescript)) {
289
339
  const { project } = job;
290
340
  const output = job.checkOnly ? { noEmit: true } : {
291
341
  noEmit: false,
@@ -300,12 +350,13 @@ function temporaryConfig(job, typescript) {
300
350
  composite: false,
301
351
  incremental: false
302
352
  };
303
- if (project.config !== void 0) {
304
- const declarations = typescript ? declarationFiles(typescript, project.config) : [];
353
+ if (project.config !== void 0 && settings !== void 0) {
354
+ const roots = extendedTypeRoots(project.config, job.folder, settings);
305
355
  return {
306
356
  extends: project.config,
307
- compilerOptions: { ...output, rewriteRelativeImportExtensions: true },
308
- files: [project.file, ...declarations.filter((name) => name !== project.file)],
357
+ // A JavaScript file runs also when the tsconfig.json does not allow JavaScript.
358
+ compilerOptions: { ...output, ...roots, rewriteRelativeImportExtensions: true, ...isJavaScriptFile(project.file) ? { allowJs: true } : {} },
359
+ files: [.../* @__PURE__ */ new Set([project.file, ...settings.declarations, ...addedFiles(settings)])],
309
360
  include: []
310
361
  };
311
362
  }
@@ -339,9 +390,10 @@ function runTsc(install2, typescript, args, file) {
339
390
  }
340
391
  function compile(job) {
341
392
  const typescript = loadTypeScript(job.typescript);
393
+ const settings = jobSettings(job, typescript);
342
394
  fs2.mkdirSync(job.folder, { recursive: true });
343
395
  const config = path3.join(job.folder, "tsconfig.json");
344
- fs2.writeFileSync(config, `${JSON.stringify(temporaryConfig(job, typescript), null, 2)}
396
+ fs2.writeFileSync(config, `${JSON.stringify(temporaryConfig(job, typescript, settings), null, 2)}
345
397
  `);
346
398
  if (!job.checkOnly) {
347
399
  fs2.mkdirSync(outFolder(job), { recursive: true });
@@ -349,15 +401,27 @@ function compile(job) {
349
401
  if (type !== void 0) fs2.writeFileSync(path3.join(outFolder(job), "package.json"), `${JSON.stringify({ type })}
350
402
  `);
351
403
  }
352
- const virtual = job.project.config === void 0 && job.project.packageRoot === void 0;
404
+ const virtual = job.project.packageRoot === void 0 && (settings === void 0 || settings.ts);
353
405
  return runTsc(job.typescript, typescript, ["--project", config], virtual ? job.project.file : void 0);
354
406
  }
355
- function checkProject(install2, config) {
407
+ function checkProject(install2, config, folder) {
356
408
  const typescript = loadTypeScript(install2);
357
- const settings = parseJsonc(fs2.readFileSync(config, "utf8"));
358
- const references = Array.isArray(settings?.references) && settings.references.length > 0;
359
- const args = references ? ["--build", config] : ["--project", config, "--noEmit"];
360
- return runTsc(install2, typescript, args);
409
+ const settings = projectConfig(install2, typescript, config);
410
+ const virtual = settings.ts && findUp(path3.dirname(config), "package.json", fs2.existsSync) === void 0 ? config : void 0;
411
+ const references = parseJsonc(fs2.readFileSync(config, "utf8"))?.references;
412
+ if (Array.isArray(references) && references.length > 0) return runTsc(install2, typescript, ["--build", config], virtual);
413
+ const added = addedFiles(settings);
414
+ if (added.length === 0) return runTsc(install2, typescript, ["--project", config, "--noEmit"], virtual);
415
+ const temporary = path3.join(folder, "tsconfig.json");
416
+ const json = {
417
+ extends: config,
418
+ compilerOptions: { noEmit: true, ...extendedTypeRoots(config, folder, settings) },
419
+ files: [.../* @__PURE__ */ new Set([...settings.files, ...added])],
420
+ include: []
421
+ };
422
+ fs2.writeFileSync(temporary, `${JSON.stringify(json, null, 2)}
423
+ `);
424
+ return runTsc(install2, typescript, ["--project", temporary], virtual);
361
425
  }
362
426
 
363
427
  // src/cli/completion.ts
@@ -430,6 +494,46 @@ var import_node_child_process3 = require("node:child_process");
430
494
  var fs3 = __toESM(require("node:fs"));
431
495
  var os = __toESM(require("node:os"));
432
496
  var path4 = __toESM(require("node:path"));
497
+
498
+ // src/cli/ask.ts
499
+ var import_node_fs = require("node:fs");
500
+ var import_node_tty = require("node:tty");
501
+ function canAsk() {
502
+ return (0, import_node_tty.isatty)(0) && (0, import_node_tty.isatty)(1);
503
+ }
504
+ function readLine() {
505
+ const bytes = [];
506
+ const buffer = new Uint8Array(1);
507
+ for (; ; ) {
508
+ let count;
509
+ try {
510
+ count = (0, import_node_fs.readSync)(0, buffer, 0, 1, null);
511
+ } catch (error) {
512
+ if (error.code === "EAGAIN") {
513
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
514
+ continue;
515
+ }
516
+ throw error;
517
+ }
518
+ if (count !== 1) break;
519
+ if (buffer[0] === 10) return new TextDecoder().decode(new Uint8Array(bytes)).replace(/\r$/, "");
520
+ bytes.push(buffer[0]);
521
+ }
522
+ return bytes.length > 0 ? new TextDecoder().decode(new Uint8Array(bytes)) : null;
523
+ }
524
+ function askYesNo(question) {
525
+ (0, import_node_fs.writeSync)(1, `${question} [Y/n] `);
526
+ const answer = (readLine() ?? "n").trim().toLowerCase();
527
+ return answer === "" || answer === "y" || answer === "yes";
528
+ }
529
+ function askText(question, fallback) {
530
+ (0, import_node_fs.writeSync)(1, `${question} [${fallback}] `);
531
+ const answer = (readLine() ?? "").trim();
532
+ return answer === "" ? fallback : answer;
533
+ }
534
+
535
+ // src/cli/init.ts
536
+ var NODE_TYPES_COMMAND = ["npm", "install", "--save-dev", "@types/node"];
433
537
  function tscInit(install2) {
434
538
  const typescript = loadTypeScript(install2);
435
539
  if (!typescript) {
@@ -461,7 +565,7 @@ function tscInit(install2) {
461
565
  }, ["--init"]);
462
566
  return { text, output, code };
463
567
  }
464
- function adapt(text, browser) {
568
+ function adapt(text) {
465
569
  const lines = text.split("\n");
466
570
  const removed = /^\s*"(noUncheckedIndexedAccess|declaration|declarationMap|jsx)":/;
467
571
  const out = [];
@@ -476,11 +580,16 @@ function adapt(text, browser) {
476
580
  break;
477
581
  }
478
582
  }
479
- if (browser) {
480
- out.push(`${indent}// For the browser (ts --init --browser):`, `${indent}"lib": ["esnext", "dom"],`, `${indent}"types": [],`);
481
- } else {
482
- out.push(`${indent}// For Node.js (ts --init), with npm install -D @types/node:`, `${indent}"lib": ["esnext"],`, `${indent}"types": ["node"],`);
483
- }
583
+ out.push(
584
+ `${indent}// For Node.js (ts --init), with npm install -D @types/node:`,
585
+ `${indent}"lib": ["esnext"],`,
586
+ `${indent}"types": ["node"],`,
587
+ `${indent}// An import names the .ts file, as Node.js needs it; tsc writes .js:`,
588
+ `${indent}"rewriteRelativeImportExtensions": true,`,
589
+ `${indent}// A project of ts: ts and the editor know prompt(), alert() and confirm(),`,
590
+ `${indent}// and every file is an ES module:`,
591
+ `${indent}"plugins": [{ "name": "${PLUGIN_NAME}" }],`
592
+ );
484
593
  while (i + 1 < lines.length && /^\s*\/\/ ("lib"|"types"|and npm install)/.test(lines[i + 1])) i++;
485
594
  continue;
486
595
  }
@@ -488,7 +597,26 @@ function adapt(text, browser) {
488
597
  }
489
598
  return out.join("\n");
490
599
  }
491
- function init(install2, browser) {
600
+ function writePackageJson() {
601
+ if (findUp(process.cwd(), "package.json", fs3.existsSync) !== void 0) return void 0;
602
+ fs3.writeFileSync(path4.join(process.cwd(), "package.json"), `${JSON.stringify({ type: "module" }, null, 2)}
603
+ `);
604
+ return 'Created a new package.json with "type": "module", so every file is an ES module';
605
+ }
606
+ function ensureNodeTypes() {
607
+ if (packageFolder("@types/node", process.cwd()) !== void 0) return 0;
608
+ const command = NODE_TYPES_COMMAND.join(" ");
609
+ if (!canAsk()) {
610
+ console.log(`The types of Node.js (@types/node) are not installed. Run ${command} in this folder.`);
611
+ return 0;
612
+ }
613
+ console.log("The types of Node.js (@types/node) are not installed.");
614
+ if (!askYesNo(`Run ${command} now?`)) return 0;
615
+ console.log(`> ${command}`);
616
+ const [npm, ...args] = NODE_TYPES_COMMAND;
617
+ return (0, import_node_child_process3.spawnSync)(npm, args, { stdio: "inherit" }).status ?? 1;
618
+ }
619
+ function init(install2) {
492
620
  const target = path4.join(process.cwd(), "tsconfig.json");
493
621
  if (fs3.existsSync(target)) {
494
622
  console.error(`error TS5054: A 'tsconfig.json' file is already defined at: '${target}'.`);
@@ -499,52 +627,18 @@ function init(install2, browser) {
499
627
  process.stdout.write(result.output);
500
628
  return result.code || 1;
501
629
  }
502
- fs3.writeFileSync(target, adapt(result.text, browser));
503
- process.stdout.write(result.output);
504
- return 0;
630
+ fs3.writeFileSync(target, adapt(result.text));
631
+ const line = install2.major >= 7 ? writePackageJson() : void 0;
632
+ const created = /^.*Created a new tsconfig\.json.*$/m;
633
+ if (line === void 0) process.stdout.write(result.output);
634
+ else process.stdout.write(created.test(result.output) ? result.output.replace(created, (text) => `${text}
635
+ ${line}`) : `${result.output}${line}
636
+ `);
637
+ return ensureNodeTypes();
505
638
  }
506
639
 
507
640
  // src/cli/install.ts
508
641
  var import_node_child_process4 = require("node:child_process");
509
-
510
- // src/cli/ask.ts
511
- var import_node_fs = require("node:fs");
512
- var import_node_tty = require("node:tty");
513
- function canAsk() {
514
- return (0, import_node_tty.isatty)(0) && (0, import_node_tty.isatty)(1);
515
- }
516
- function readLine() {
517
- const bytes = [];
518
- const buffer = new Uint8Array(1);
519
- for (; ; ) {
520
- let count;
521
- try {
522
- count = (0, import_node_fs.readSync)(0, buffer, 0, 1, null);
523
- } catch (error) {
524
- if (error.code === "EAGAIN") {
525
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20);
526
- continue;
527
- }
528
- throw error;
529
- }
530
- if (count !== 1) break;
531
- if (buffer[0] === 10) return new TextDecoder().decode(new Uint8Array(bytes)).replace(/\r$/, "");
532
- bytes.push(buffer[0]);
533
- }
534
- return bytes.length > 0 ? new TextDecoder().decode(new Uint8Array(bytes)) : null;
535
- }
536
- function askYesNo(question) {
537
- (0, import_node_fs.writeSync)(1, `${question} [Y/n] `);
538
- const answer = (readLine() ?? "n").trim().toLowerCase();
539
- return answer === "" || answer === "y" || answer === "yes";
540
- }
541
- function askText(question, fallback) {
542
- (0, import_node_fs.writeSync)(1, `${question} [${fallback}] `);
543
- const answer = (readLine() ?? "").trim();
544
- return answer === "" ? fallback : answer;
545
- }
546
-
547
- // src/cli/install.ts
548
642
  var TIMEOUT = 4e3;
549
643
  async function fetchJson(url2, headers = {}) {
550
644
  const response = await fetch(url2, { headers, signal: AbortSignal.timeout(TIMEOUT) });
@@ -638,14 +732,15 @@ function ensurePackages(folder) {
638
732
  const missing = missingPackages(folder);
639
733
  if (!missing) return true;
640
734
  const command = `npm ${installArguments(missing.root).join(" ")}`;
641
- const where = path5.relative(process.cwd(), missing.root) || ".";
735
+ const relative3 = path5.relative(process.cwd(), missing.root);
736
+ const where = relative3 === "" ? "in this folder" : `in ${relative3}`;
642
737
  const names2 = missing.missing.join(", ");
643
738
  if (!canAsk()) {
644
- console.error(`The packages of this course are not installed (${names2}). Run ${command} in ${where}.`);
739
+ console.error(`The packages of this course are not installed (${names2}). Run ${command} ${where}.`);
645
740
  return false;
646
741
  }
647
742
  console.log(`The packages of this course are not installed (${names2}).`);
648
- if (!askYesNo(`Run ${command} in ${where} now?`)) return false;
743
+ if (!askYesNo(`Run ${command} ${where} now?`)) return false;
649
744
  console.log(`> ${command}`);
650
745
  const result = (0, import_node_child_process5.spawnSync)("npm", installArguments(missing.root), { cwd: missing.root, stdio: "inherit" });
651
746
  return result.status === 0 && missingPackages(folder) === void 0;
@@ -807,7 +902,7 @@ async function main(argv) {
807
902
  return 2;
808
903
  }
809
904
  if (command.help) {
810
- console.log(helpText("0.2.0"));
905
+ console.log(helpText("0.2.1"));
811
906
  return 0;
812
907
  }
813
908
  if (command.completion !== void 0) {
@@ -817,7 +912,7 @@ async function main(argv) {
817
912
  if (command.version) {
818
913
  const lookup = findTypeScript(process.cwd());
819
914
  const typescript2 = "found" in lookup ? describe(lookup.found) : "TypeScript: not installed";
820
- console.log(`ts ${"0.2.0"}
915
+ console.log(`ts ${"0.2.1"}
821
916
  ${typescript2}
822
917
  Node.js ${process.version}`);
823
918
  return 0;
@@ -829,17 +924,24 @@ Node.js ${process.version}`);
829
924
  }
830
925
  if (command.init) {
831
926
  const typescript2 = await typeScriptFor(process.cwd());
832
- return typescript2 ? init(typescript2, command.browser) : 1;
927
+ return typescript2 ? init(typescript2) : 1;
833
928
  }
834
929
  const file = entryFile(command.file);
835
930
  if (file === void 0) {
836
931
  if (command.check && fs6.existsSync("tsconfig.json")) {
837
932
  const typescript2 = await typeScriptFor(process.cwd());
838
- return typescript2 ? checkProject(typescript2, path7.resolve("tsconfig.json")) : 1;
933
+ if (!typescript2) return 1;
934
+ const config = path7.resolve("tsconfig.json");
935
+ const runFolder = createRunFolder(projectOfFile(config));
936
+ try {
937
+ return checkProject(typescript2, config, runFolder.folder);
938
+ } finally {
939
+ removeRunFolder(runFolder);
940
+ }
839
941
  }
840
942
  console.error(`No ${ENTRY_FILES.join(" or ")} in this folder.
841
943
  `);
842
- console.error(helpText("0.2.0"));
944
+ console.error(helpText("0.2.1"));
843
945
  return 1;
844
946
  }
845
947
  if (!fs6.existsSync(file)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimblescape/typescript",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "The command ts: checks a TypeScript file with tsc, compiles it and runs it with Node.js, with the global functions prompt(), alert() and confirm().",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -4,8 +4,8 @@
4
4
 
5
5
  // The types of the polyfills of ts (src/runtime/polyfill.ts): prompt(), alert() and confirm() are global functions in
6
6
  // every program that ts runs, as in Deno and the browser. Without a tsconfig.json, ts and the editor plugin add this
7
- // type package ("types": ["node", "nimblescape-globals"]); a project with its own tsconfig.json declares them itself,
8
- // for example with a copy of these lines in a globals.d.ts.
7
+ // type package ("types": ["node", "nimblescape-globals"]); with a tsconfig.json, they add this file to the files of
8
+ // the project, unless the project has the types of the browser (lib dom), which declare the three functions.
9
9
 
10
10
  /**
11
11
  * Shows `message`, waits for one line of the keyboard and returns it without the line break. An empty line returns