@lakutata/cli 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -57,7 +57,8 @@ the command fails.
57
57
  A template package keeps its files in its `template/` directory. npm packages cannot hold some files under their real
58
58
  names, so `_gitignore`, `_npmrc`… are renamed `.gitignore`, `.npmrc`… when the project is created. The text files are
59
59
  filled with the information of the project: `{$APP_NAME}`, `{$APP_ID}`, `{$APP_DESC}`, `{$APP_AUTHOR}`,
60
- `{$APP_LICENSE}` and `{$LAKUTATA_VERSION}` (the version of the framework).
60
+ `{$APP_LICENSE}` and `{$LAKUTATA_VERSION}` (the minor version of the framework, `3.1.0` for 3.1.2: a patch releases
61
+ only the packages it changes, so `^{$LAKUTATA_VERSION}` finds every package of lakutata).
61
62
 
62
63
  A template can have optional features, which `lakutata create --with <features>` enables (or which the user chooses in
63
64
  a terminal):
@@ -213,6 +214,34 @@ Packages are still published with `npm publish -w <package>` (the packages `laku
213
214
  `lerna.json` with `"version": "independent"` cannot be bumped), write changelogs from the commits or cache builds:
214
215
  workspaces that need these keep lerna (or nx, turborepo) alongside it.
215
216
 
217
+ ## Native libraries
218
+
219
+ ### `lakutata ffi generate <header>`
220
+
221
+ Generate the class of a native library of `@lakutata/ffi` from its C header: its declarations, a property by function
222
+ with its TypeScript type, an interface by struct and union, its integer macros as constants. The header is run through
223
+ the preprocessor of the C compiler (`cc -E`; `cl /P` on Windows, in the Developer Command Prompt of Visual Studio); the
224
+ declarations of the library are kept (the ones of the header, and of the headers of its directory unless it is a
225
+ directory of the system), with the types of the system they use. The functions are then checked against the library:
226
+ the ones it does not export, or that cannot be called (a `va_list` parameter), are left out. The command lists what
227
+ it left out and why. The tool loads `@lakutata/ffi` from the project: install it there.
228
+
229
+ | Option | |
230
+ |---|---|
231
+ | `--library <name or file>` | the library the functions are checked against: a name, as `@Library` takes it, or a file; the name of the header by default (`sqlite3.h`: `sqlite3`; zlib needs `--library z`). When it cannot be loaded, the class is generated without the check, with a warning |
232
+ | `--class-name <name>` | the name of the class, the name of the header in PascalCase by default |
233
+ | `--out <file>` | the file written, `<class>.ts` in the current directory by default |
234
+ | `--force` | replace the file when it exists |
235
+ | `--include <directories...>` | the directories of the headers it includes (`-I`) |
236
+ | `--define <macros...>` | the macros defined before it: `NAME` or `NAME=value` (`-D`) |
237
+ | `--own <directories...>` | more directories whose headers are the ones of the library |
238
+ | `--compiler <compiler>` | the C compiler whose preprocessor reads the header: `cc`, `gcc`, `clang`, `cl` (the one of `CC` by default, else `cl` on Windows and `cc` elsewhere) |
239
+ | `--no-check` | do not check the functions against the library |
240
+
241
+ The class is generated for the platform the command runs on. A header does not say which pointers are output
242
+ parameters, nor which functions block: mark them in the file generated (`_Out_`, `{async: true}`). See the guide to
243
+ native libraries ([FFI](https://lakutata.com/guide/FFI), section 9).
244
+
216
245
  ## Bundles and executables
217
246
 
218
247
  These commands pack the application in the current directory, which must be compiled first (by `tsc`, which emits the
@@ -250,8 +279,11 @@ declarations, source maps, TypeScript sources and documentation of the packages
250
279
  sources and objects of the addons (better-sqlite3 holds the source of SQLite) and what built them: the files of node-gyp
251
280
  and of CMake in `build/`, the `target` directory of cargo, the static and import libraries. The addons of the
252
281
  application itself (`require('./x.node')`, `createRequire(import.meta.url)('./x.node')`) go into the tree too. The
253
- packages of the framework, and those built on it, are always bundled with the application, whatever they depend on, so
254
- that there is one copy of the framework, its classes and its container; their native dependencies go into the tree.
282
+ packages of the framework, and those built on it, are always bundled with the application, whatever they depend on or
283
+ hold, so that there is one copy of the framework, its classes and its container. Their native dependencies go into the
284
+ tree, and so do their own addons when they are loaded by a `require` of the file, by a path from their module
285
+ (`path.join(__dirname, 'prebuilds', ...)`) or from their own directory (`node-gyp-build`); an addon loaded in another
286
+ way is named in the warnings.
255
287
  Optional dependencies that are not installed (the drivers of stores the application does not use: `peerDependenciesMeta`
256
288
  optional, `optionalDependencies`) are left out: loading one throws at run time, as it would without the bundle.
257
289
 
package/README.zh-CN.md CHANGED
@@ -42,7 +42,7 @@ lakutata create --name my-service --template service --with i18n --yes
42
42
 
43
43
  官方模板取与工具主版本号相同的最新正式版。下载过的模板会缓存起来:连不上仓库时,使用缓存中这个主版本的最新版本;缓存中也没有时,命令失败。
44
44
 
45
- 模板包把文件放在它的 `template/` 目录中。有些文件无法以真实的文件名放进 npm 包,所以 `_gitignore`、`_npmrc` 等文件会在创建项目时改名为 `.gitignore`、`.npmrc` 等。文本文件中会填入项目的信息:`{$APP_NAME}`、`{$APP_ID}`、`{$APP_DESC}`、`{$APP_AUTHOR}`、`{$APP_LICENSE}` 和 `{$LAKUTATA_VERSION}`(框架的版本)。
45
+ 模板包把文件放在它的 `template/` 目录中。有些文件无法以真实的文件名放进 npm 包,所以 `_gitignore`、`_npmrc` 等文件会在创建项目时改名为 `.gitignore`、`.npmrc` 等。文本文件中会填入项目的信息:`{$APP_NAME}`、`{$APP_ID}`、`{$APP_DESC}`、`{$APP_AUTHOR}`、`{$APP_LICENSE}` 和 `{$LAKUTATA_VERSION}`(框架的次版本,比如框架是 3.1.2 时为 `3.1.0`:补丁版只发布改动过的包,写成 `^{$LAKUTATA_VERSION}` 才能找到 lakutata 的每个包)。
46
46
 
47
47
  模板可以有可选功能,由 `lakutata create --with <features>` 启用(或者由用户在终端里选择):
48
48
 
@@ -130,6 +130,26 @@ lakutata create --name my-service --template service --with i18n --yes
130
130
 
131
131
  包仍然用 `npm publish -w <package>` 发布(`lakutata add` 创建的包是私有的:请先删掉 `"private": true`)。工具只读写 `lerna.json` 中的 `version`:包来自根目录 `package.json` 的 `workspaces`,或者 `pnpm-workspace.yaml`。它不支持给各个包分别设置版本号(`"version": "independent"` 的 `lerna.json` 无法递增版本),不根据提交生成变更日志,也不缓存构建结果:需要这些功能的工作区可以同时保留 lerna(或 nx、turborepo)。
132
132
 
133
+ ## 原生库
134
+
135
+ ### `lakutata ffi generate <header>`
136
+
137
+ 根据 C 头文件生成 `@lakutata/ffi` 原生库的类:包括它的声明、每个函数一个带 TypeScript 类型的属性、每个结构体和联合一个接口,整数宏作为常量。头文件先经过 C 编译器的预处理器(`cc -E`;在 Windows 上是 `cl /P`,请在 Visual Studio 的开发者命令提示符里运行);保留库自己的声明(头文件里的,以及它所在目录里其他头文件的,这个目录是系统目录时除外),以及这些声明用到的系统类型。然后用库检查这些函数:库里没有导出的函数和不能调用的函数(参数是 `va_list`)会被去掉。命令会列出去掉了哪些声明,以及原因。工具从项目里加载 `@lakutata/ffi`:请把它装在项目里。
138
+
139
+ | 选项 | |
140
+ |---|---|
141
+ | `--library <name or file>` | 用来检查函数的库:`@Library` 接受的库名,或一个文件;默认和头文件同名(`sqlite3.h` 对应 `sqlite3`;zlib 要写 `--library z`)。库加载不了时,照样生成类,只是不做检查,并给出警告 |
142
+ | `--class-name <name>` | 类的名称,默认是头文件名的 PascalCase 形式 |
143
+ | `--out <file>` | 要写入的文件,默认是当前目录下的 `<类名>.ts` |
144
+ | `--force` | 文件已存在时覆盖它 |
145
+ | `--include <directories...>` | 头文件引用的其他头文件所在的目录(`-I`) |
146
+ | `--define <macros...>` | 在头文件之前定义的宏:`NAME` 或 `NAME=value`(`-D`) |
147
+ | `--own <directories...>` | 其他属于这个库的头文件的目录 |
148
+ | `--compiler <compiler>` | 用来预处理头文件的 C 编译器:`cc`、`gcc`、`clang`、`cl`(默认取 `CC` 环境变量,没有时在 Windows 上是 `cl`,其他系统上是 `cc`) |
149
+ | `--no-check` | 不用库检查函数 |
150
+
151
+ 类是针对运行命令的平台生成的。头文件看不出哪些指针是输出参数,也看不出哪些函数会阻塞:请在生成的文件里自己标出来(`_Out_`、`{async: true}`)。见原生库的指南([FFI](https://lakutata.com/zh/guide/FFI) 第 9 节)。
152
+
133
153
  ## 单文件包和可执行文件
134
154
 
135
155
  这些命令打包当前目录中的应用,应用必须先编译好(用 `tsc`,它会生成装饰器的元数据)。实际的工作由 `@lakutata/builder` 完成:优先用项目中安装的那份(`npm install --save-dev @lakutata/builder`,需要打包应用的模板都是这样做的),否则用随工具一起安装的那份(全局安装工具时不会带上它,因为它只是工具的可选 peer 依赖)。脚本也可以调用它的函数(`bundleProject`、`buildExecutable`)来打包应用。
@@ -147,7 +167,7 @@ lakutata create --name my-service --template service --with i18n --yes
147
167
 
148
168
  单文件包是 CommonJS 模块,因为 Node.js 的可执行文件要运行它们。应用的 ES 模块会被转换,所以不能使用顶层 `await`:请在异步函数里 await。
149
169
 
150
- 原生插件(`.node` 文件)无法打进单文件包。打包时,工具会找出含有插件的包,以及通过计算出的路径加载插件的包(例如 sharp),把它们连同运行时加载的内容(它们 require 的 JavaScript,以及插件所链接的共享库所在的包,例如 libvips)排除在单文件包之外,按 `node_modules` 的结构放在 `dist/<name>.native/` 中,这个目录随单文件包一起发布。其中是运行时加载的文件:插件和共享库所在包的全部文件;其他包则是它们加载的 JavaScript(逐个跟踪得出)和其他文件(它们读取的数据);以及通过无法跟踪的路径加载自身文件的包(`require(path.join(__dirname, name))`)的全部文件。这些包的类型声明、source map、TypeScript 源码和文档会被排除,插件的 C 和 C++ 源码和目标文件(better-sqlite3 带有 SQLite 的源码),以及构建它们时产生的文件也会被排除:`build/` 中 node-gyp 和 CMake 的文件、cargo 的 `target` 目录、静态库和导入库。应用自己的插件(`require('./x.node')`、`createRequire(import.meta.url)('./x.node')`)也放进这个目录。框架的包以及基于框架的包,不论依赖什么,总是和应用一起打进单文件包,这样框架、它的类和它的容器都只有一份;它们的原生依赖放进这个目录。没有安装的可选依赖(应用没有用到的存储驱动:`peerDependenciesMeta` 中标为 optional 的依赖,以及 `optionalDependencies`)会被排除:运行时加载它会抛出异常,和不打包时一样。
170
+ 原生插件(`.node` 文件)无法打进单文件包。打包时,工具会找出含有插件的包,以及通过计算出的路径加载插件的包(例如 sharp),把它们连同运行时加载的内容(它们 require 的 JavaScript,以及插件所链接的共享库所在的包,例如 libvips)排除在单文件包之外,按 `node_modules` 的结构放在 `dist/<name>.native/` 中,这个目录随单文件包一起发布。其中是运行时加载的文件:插件和共享库所在包的全部文件;其他包则是它们加载的 JavaScript(逐个跟踪得出)和其他文件(它们读取的数据);以及通过无法跟踪的路径加载自身文件的包(`require(path.join(__dirname, name))`)的全部文件。这些包的类型声明、source map、TypeScript 源码和文档会被排除,插件的 C 和 C++ 源码和目标文件(better-sqlite3 带有 SQLite 的源码),以及构建它们时产生的文件也会被排除:`build/` 中 node-gyp 和 CMake 的文件、cargo 的 `target` 目录、静态库和导入库。应用自己的插件(`require('./x.node')`、`createRequire(import.meta.url)('./x.node')`)也放进这个目录。框架的包以及基于框架的包,不论依赖什么、自己是否带有插件,总是和应用一起打进单文件包,这样框架、它的类和它的容器都只有一份。它们的原生依赖放进这个目录;它们自己的插件也放进这个目录,前提是插件通过直接 `require` 插件文件、从模块位置构造的路径(`path.join(__dirname, 'prebuilds', ...)`)或包自身的目录(`node-gyp-build`)加载,用其他方式加载的插件会列在警告中。没有安装的可选依赖(应用没有用到的存储驱动:`peerDependenciesMeta` 中标为 optional 的依赖,以及 `optionalDependencies`)会被排除:运行时加载它会抛出异常,和不打包时一样。
151
171
 
152
172
  打包器不会跟踪应用按路径读取的文件:它单独运行的脚本(worker:`new Worker()`、传入文件的 worker 池;子进程:`fork()`;按路径加载的模块:`require(path.join(__dirname, 'x.js'))`),以及它的数据(`fs.readFileSync(path.join(__dirname, 'defaults.json'))`、存放 schema 的目录)。而且单文件包并不在原来各个模块所在的位置:在 `build/config/` 中的模块里,`path.resolve(__dirname, '../../schema')` 是项目的 `schema/` 目录,但打包到 `dist/` 之后就不是了。工具会在应用的模块和它所用包的模块中,找出所有由模块位置构造出的文件路径,不论写在哪里,并改写这些表达式,让它们指向 `.native` 目录中的文件(`dist/<name>.native/.lakutata/files/`,目录结构与项目中相同,从项目外部链接进来的包放在 `node_modules/` 下):
153
173
 
@@ -8,13 +8,14 @@ import { WorkspaceController } from './controllers/WorkspaceController.js';
8
8
  import { PackController } from './controllers/PackController.js';
9
9
  import { MigrationController } from './controllers/MigrationController.js';
10
10
  import { NativeController } from './controllers/NativeController.js';
11
+ import { FFIController } from './controllers/FFIController.js';
11
12
  import { I18nController } from './controllers/I18nController.js';
12
13
  import { LANGUAGE_VARIABLE, LanguageMiddleware } from './lib/Language.js';
13
14
  import { toolPackage } from './lib/Versions.js';
14
15
  /**
15
16
  * The controllers of the commands of the tool
16
17
  */
17
- export const CONTROLLERS = [CommandLineController, WorkspaceController, PackController, MigrationController, NativeController, I18nController];
18
+ export const CONTROLLERS = [CommandLineController, WorkspaceController, PackController, MigrationController, NativeController, FFIController, I18nController];
18
19
  /**
19
20
  * The messages of the tool in the languages besides English (its texts in the code): its help, its prompts
20
21
  */
@@ -22,7 +23,8 @@ export const CONTROLLERS = [CommandLineController, WorkspaceController, PackCont
22
23
  * The groups of commands: lakutata package --help lists the commands of the group package
23
24
  */
24
25
  export const COMMAND_GROUPS = {
25
- package: 'Package the compiled application: into one JavaScript file, an executable or its installers'
26
+ package: 'Package the compiled application: into one JavaScript file, an executable or its installers',
27
+ ffi: 'Generate the classes of the native libraries of @lakutata/ffi from their C headers'
26
28
  };
27
29
  export const MESSAGES = { 'zh-CN': zhCN, 'zh-TW': zhTW };
28
30
  /**
@@ -0,0 +1,54 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
11
+ import path from 'node:path';
12
+ import { Controller } from 'lakutata/com/entrypoint';
13
+ import { CLIAction } from 'lakutata/decorator/ctrl';
14
+ import { FFIGenerateDTO } from '../dtos/FFIGenerateDTO.js';
15
+ import { loadGenerator } from '../lib/FFI.js';
16
+ import { done, warn } from '../lib/Output.js';
17
+ /**
18
+ * The commands of the native libraries (@lakutata/ffi)
19
+ */
20
+ export class FFIController extends Controller {
21
+ async generate(input) {
22
+ const generator = await loadGenerator(process.cwd());
23
+ const { className, code, report } = await generator.generateNativeLibrary({
24
+ header: input.header,
25
+ library: input.library,
26
+ className: input.className,
27
+ include: input.include,
28
+ define: input.define,
29
+ own: input.own,
30
+ compiler: input.compiler,
31
+ check: input.check
32
+ });
33
+ const file = path.resolve(input.out ?? `${className}.ts`);
34
+ if (existsSync(file) && !input.force)
35
+ throw new Error(`${path.relative(process.cwd(), file)} exists: give --force to replace it`);
36
+ mkdirSync(path.dirname(file), { recursive: true });
37
+ writeFileSync(file, code);
38
+ const count = (number, noun) => `${number} ${noun}${number === 1 ? '' : 's'}`;
39
+ done(`Generated ${path.relative(process.cwd(), file)} (${className}): ${count(report.functions, 'function')}, ${count(report.variables, 'variable')}, ${count(report.interfaces, 'interface')}, ${count(report.constants, 'constant')}${report.library ? `, checked against ${report.library}` : ''}`);
40
+ if (!report.library)
41
+ warn(input.check ? `The functions were not checked against the library: ${report.unchecked}; give it with --library` : 'The functions were not checked against the library (--no-check)');
42
+ if (report.skipped.length)
43
+ warn(`Left out ${count(report.skipped.length, 'declaration')} of the library:`);
44
+ for (const skipped of report.skipped)
45
+ warn(` ${skipped.name}${skipped.file ? ` (${path.relative(process.cwd(), skipped.file)}:${skipped.line})` : ''}: ${skipped.reason}`);
46
+ return undefined;
47
+ }
48
+ }
49
+ __decorate([
50
+ CLIAction('ffi generate <header>', FFIGenerateDTO, { description: 'Generate the class of a native library from its C header: a property by function, an interface by struct, the constants, with their TypeScript types' }),
51
+ __metadata("design:type", Function),
52
+ __metadata("design:paramtypes", [FFIGenerateDTO]),
53
+ __metadata("design:returntype", Promise)
54
+ ], FFIController.prototype, "generate", null);
@@ -0,0 +1,53 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { DTO } from 'lakutata';
11
+ import { Expect } from 'lakutata/decorator/dto';
12
+ export class FFIGenerateDTO extends DTO {
13
+ }
14
+ __decorate([
15
+ Expect(DTO.String().required().description('The C header of the library')),
16
+ __metadata("design:type", String)
17
+ ], FFIGenerateDTO.prototype, "header", void 0);
18
+ __decorate([
19
+ Expect(DTO.String().optional().description('The library its functions are checked against: a name, as @Library takes it, or a file (the name of the header by default)')),
20
+ __metadata("design:type", String)
21
+ ], FFIGenerateDTO.prototype, "library", void 0);
22
+ __decorate([
23
+ Expect(DTO.String().optional().description('The name of the class (the name of the header in PascalCase by default)')),
24
+ __metadata("design:type", String)
25
+ ], FFIGenerateDTO.prototype, "className", void 0);
26
+ __decorate([
27
+ Expect(DTO.String().optional().description('The TypeScript file written (<class>.ts in the current directory by default)')),
28
+ __metadata("design:type", String)
29
+ ], FFIGenerateDTO.prototype, "out", void 0);
30
+ __decorate([
31
+ Expect(DTO.Array(DTO.String()).optional().description('The directories of the headers it includes (-I)')),
32
+ __metadata("design:type", Array)
33
+ ], FFIGenerateDTO.prototype, "include", void 0);
34
+ __decorate([
35
+ Expect(DTO.Array(DTO.String()).optional().description('The macros defined before it: NAME or NAME=value (-D)')),
36
+ __metadata("design:type", Array)
37
+ ], FFIGenerateDTO.prototype, "define", void 0);
38
+ __decorate([
39
+ Expect(DTO.Array(DTO.String()).optional().description('More directories whose headers are the ones of the library (the header already is, and so are the headers of its directory unless that is a directory of the system)')),
40
+ __metadata("design:type", Array)
41
+ ], FFIGenerateDTO.prototype, "own", void 0);
42
+ __decorate([
43
+ Expect(DTO.String().optional().description('The C compiler whose preprocessor reads the header: cc, gcc, clang, cl (the one of CC by default, else cl on Windows and cc elsewhere)')),
44
+ __metadata("design:type", String)
45
+ ], FFIGenerateDTO.prototype, "compiler", void 0);
46
+ __decorate([
47
+ Expect(DTO.Boolean().optional().default(true).description('Check the functions and the variables against the library, leaving out the ones it cannot give')),
48
+ __metadata("design:type", Boolean)
49
+ ], FFIGenerateDTO.prototype, "check", void 0);
50
+ __decorate([
51
+ Expect(DTO.Boolean().optional().default(false).description('Replace the file when it exists')),
52
+ __metadata("design:type", Boolean)
53
+ ], FFIGenerateDTO.prototype, "force", void 0);
@@ -0,0 +1,21 @@
1
+ import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ /**
5
+ * The generator of a project: the one of the @lakutata/ffi installed in the project (its node_modules, the ones of its
6
+ * workspace), else the one installed with the tool
7
+ * @param project
8
+ */
9
+ export async function loadGenerator(project) {
10
+ for (const base of [path.join(project, 'package.json'), import.meta.url]) {
11
+ let entry;
12
+ try {
13
+ entry = createRequire(base).resolve('@lakutata/ffi/generator');
14
+ }
15
+ catch {
16
+ continue;
17
+ }
18
+ return await import(pathToFileURL(entry).href);
19
+ }
20
+ throw new Error('@lakutata/ffi is not installed: install it in the project (npm install @lakutata/ffi)');
21
+ }
@@ -17,7 +17,7 @@ import { TemplateCatalog } from './TemplateCatalog.js';
17
17
  import { runCommand } from './Processes.js';
18
18
  import { done, step, warn } from './Output.js';
19
19
  import { applyFeatureBlocks, applyFeatureFiles, projectPath } from './TemplateFeatures.js';
20
- import { frameworkVersion } from './Versions.js';
20
+ import { templateVersion } from './Versions.js';
21
21
  /**
22
22
  * The directories of a template which are not copied
23
23
  */
@@ -154,7 +154,7 @@ let ProjectCreator = class ProjectCreator extends Component {
154
154
  $APP_DESC: plan.description,
155
155
  $APP_AUTHOR: plan.author,
156
156
  $APP_LICENSE: plan.license,
157
- $LAKUTATA_VERSION: frameworkVersion()
157
+ $LAKUTATA_VERSION: templateVersion()
158
158
  }, new Set(plan.features));
159
159
  done('Project information filled');
160
160
  if (plan.packageManager !== 'none') {
@@ -15,6 +15,16 @@ export function toolPackage() {
15
15
  export function frameworkVersion() {
16
16
  return require('lakutata/package.json').version;
17
17
  }
18
+ /**
19
+ * The version of the packages of lakutata written in the projects created, after a ^: the minor version of the
20
+ * framework (3.1.0 for 3.1.2), since a patch releases only the packages it changes; a prerelease as it is, since all
21
+ * the packages are released with it
22
+ */
23
+ export function templateVersion() {
24
+ const version = frameworkVersion();
25
+ const release = /^(\d+)\.(\d+)\.\d+$/.exec(version);
26
+ return release ? `${release[1]}.${release[2]}.0` : version;
27
+ }
18
28
  /**
19
29
  * The latest version of a package on the registry, undefined when it cannot be fetched
20
30
  * @param name
@@ -132,6 +132,26 @@
132
132
  "target": "要修改的构建目标(默认是唯一的那个)"
133
133
  }
134
134
  },
135
+ "ffi": {
136
+ "description": "根据 C 头文件生成 @lakutata/ffi 原生库的类",
137
+ "generate": {
138
+ "description": "根据 C 头文件生成原生库的类:每个函数一个属性,每个结构体一个接口,加上常量,都带有 TypeScript 类型",
139
+ "arguments": {
140
+ "header": "库的 C 头文件"
141
+ },
142
+ "options": {
143
+ "library": "用来检查函数的库:@Library 接受的库名,或一个文件(默认按头文件的名字)",
144
+ "className": "类的名称(默认是头文件名的 PascalCase 形式)",
145
+ "out": "要写入的 TypeScript 文件(默认是当前目录下的 <类名>.ts)",
146
+ "include": "头文件引用的其他头文件所在的目录(-I)",
147
+ "define": "在头文件之前定义的宏:NAME 或 NAME=value(-D)",
148
+ "own": "其他属于这个库的头文件的目录(头文件本身算库的;它所在的目录不是系统目录时,目录里的头文件也算)",
149
+ "compiler": "用来预处理头文件的 C 编译器:cc、gcc、clang、cl(默认取 CC 环境变量,没有时在 Windows 上是 cl,其他系统上是 cc)",
150
+ "check": "用库检查函数和变量,去掉库里没有的",
151
+ "force": "文件已存在时覆盖它"
152
+ }
153
+ }
154
+ },
135
155
  "i18n": {
136
156
  "description": "检查各语言的翻译、声明键名的类型,或添加一种语言",
137
157
  "arguments": {
@@ -132,6 +132,26 @@
132
132
  "target": "要修改的建置目標(預設是唯一的那個)"
133
133
  }
134
134
  },
135
+ "ffi": {
136
+ "description": "根據 C 標頭檔產生 @lakutata/ffi 原生程式庫的類別",
137
+ "generate": {
138
+ "description": "根據 C 標頭檔產生原生程式庫的類別:每個函式一個屬性,每個結構一個介面,加上常數,都帶有 TypeScript 型別",
139
+ "arguments": {
140
+ "header": "程式庫的 C 標頭檔"
141
+ },
142
+ "options": {
143
+ "library": "用來檢查函式的程式庫:@Library 接受的程式庫名稱,或一個檔案(預設依標頭檔的名稱)",
144
+ "className": "類別的名稱(預設是標頭檔名的 PascalCase 形式)",
145
+ "out": "要寫入的 TypeScript 檔案(預設是目前目錄下的 <類別名稱>.ts)",
146
+ "include": "標頭檔引入的其他標頭檔所在的目錄(-I)",
147
+ "define": "在標頭檔之前定義的巨集:NAME 或 NAME=value(-D)",
148
+ "own": "其他屬於這個程式庫的標頭檔的目錄(標頭檔本身算程式庫的;它所在的目錄不是系統目錄時,目錄裡的標頭檔也算)",
149
+ "compiler": "用來前置處理標頭檔的 C 編譯器:cc、gcc、clang、cl(預設取 CC 環境變數,沒有時在 Windows 上是 cl,其他系統上是 cc)",
150
+ "check": "用程式庫檢查函式和變數,去掉程式庫裡沒有的",
151
+ "force": "檔案已存在時覆寫它"
152
+ }
153
+ }
154
+ },
135
155
  "i18n": {
136
156
  "description": "檢查各語言的翻譯、宣告鍵名的型別,或新增一種語言",
137
157
  "arguments": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakutata/cli",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "The command line tool of the lakutata framework: create the projects from the templates, run and version the packages of the workspaces, bundle the applications and make their executables (with @lakutata/builder).",
5
5
  "keywords": [
6
6
  "lakutata",
@@ -39,18 +39,23 @@
39
39
  "dependencies": {
40
40
  "@inquirer/prompts": "^8.7.2",
41
41
  "commander": "^15.0.0",
42
- "lakutata": "3.1.1"
42
+ "lakutata": "3.1.3"
43
43
  },
44
44
  "peerDependencies": {
45
- "@lakutata/builder": "3.1.1"
45
+ "@lakutata/builder": "~3.1.3",
46
+ "@lakutata/ffi": "~3.1.2"
46
47
  },
47
48
  "peerDependenciesMeta": {
48
49
  "@lakutata/builder": {
49
50
  "optional": true
51
+ },
52
+ "@lakutata/ffi": {
53
+ "optional": true
50
54
  }
51
55
  },
52
56
  "devDependencies": {
53
- "@lakutata/builder": "3.1.1"
57
+ "@lakutata/builder": "~3.1.3",
58
+ "@lakutata/ffi": "~3.1.2"
54
59
  },
55
60
  "engines": {
56
61
  "node": "^22.13.0 || >=24.11.0"