@aventara/build 0.0.0-stage → 1.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,123 @@
1
+ # @aventara/build
2
+
3
+ ## 1.0.0-rc.8
4
+
5
+ 1.0.0-rc.5, rc.6 and rc.7 were tagged but never published; 1.0.0-rc.8 contains everything listed under them and the changes below.
6
+
7
+ ### Patch Changes
8
+
9
+ - 1c8e4ee: `aventara-build --check` says what changed when the prebuilt Contracts file is stale, in the same words as the
10
+ `AVENTARA_PREBUILT_STALE` warning at startup. Before, anything outside a single Resource read "an input that belongs
11
+ to no single Resource, the core version or the file format changed". Now it names the versions or the inputs:
12
+
13
+ ```text
14
+ aventara-build: src/generated/aventara/contracts.generated.ts is stale: it was built by @aventara/core 1.0.0-rc.7, and this is 1.0.0-rc.8. Run aventara-build (npm run aventara:prepare) and deploy the file it writes.
15
+ aventara-build: src/generated/aventara/contracts.generated.ts is stale: Resource User changed and the configuration or model outside the Resources changed since it was written. Run aventara-build (npm run aventara:prepare) and deploy the file it writes.
16
+ ```
17
+
18
+ It also names a file format or protocol version that differs, the Resources that are new or gone, and a file that
19
+ no longer reads as a prebuilt Contracts file.
20
+
21
+ - Updated dependencies [1c8e4ee]
22
+ - @aventara/core@1.0.0-rc.8
23
+
24
+ ## 1.0.0-rc.7
25
+
26
+ 1.0.0-rc.5 and 1.0.0-rc.6 were tagged but never published; 1.0.0-rc.7 contains everything listed under them and the changes below.
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies [f917c1c]
31
+ - @aventara/core@1.0.0-rc.7
32
+
33
+ ## 1.0.0-rc.6
34
+
35
+ 1.0.0-rc.5 was tagged but never published. 1.0.0-rc.6 contains everything listed under 1.0.0-rc.5 and the fix below.
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [1cf72e0]
40
+ - @aventara/core@1.0.0-rc.6
41
+
42
+ ## 1.0.0-rc.5
43
+
44
+ ### Patch Changes
45
+
46
+ - Updated dependencies [4795ff1]
47
+ - Updated dependencies [9e178ca]
48
+ - @aventara/core@1.0.0-rc.5
49
+
50
+ ## 1.0.0-rc.4
51
+
52
+ ### Patch Changes
53
+
54
+ - 7c3b65f: Build-time Contracts for serverless and edge cold starts.
55
+
56
+ - New package `@aventara/build`, bin `aventara-build`, a development dependency. It runs `src/aventara.config.ts` and
57
+ `src/aventara.adapter.ts` without a database client, compiles the Contracts with the project's `@aventara/core` and
58
+ writes `src/generated/aventara/contracts.generated.ts` (`aventaraContracts`), which a serverless or edge entry passes
59
+ to `createFramework` as `prebuilt`. A configuration that startup would refuse fails the build with the same
60
+ `CONFIG_…`/`COMPILER_…` diagnostics. `aventara-build --check` writes nothing and exits 1 when the file is missing or
61
+ stale, naming the Resources that changed.
62
+ - `@aventara/prisma7-adapter`: `describePrismaAdapter({ discovery, provider, driver })` states the adapter's passive
63
+ facts (the model, the driver's transaction capability, the referential actions, the computed fields) with no client.
64
+ `createPrismaAdapter({ ...description, client })` builds the adapter from the same facts, and still checks the client
65
+ against the discovery artifact.
66
+ - `@aventara/cli`: the scaffold's `src/aventara.config.ts` exports the configuration as a plain value
67
+ (`export const aventaraConfig = { … } as const`) with no `adapter`. A new `src/aventara.adapter.ts` default-exports
68
+ `describePrismaAdapter(…)`, and `app.module.ts` joins the two:
69
+ `config: { ...aventaraConfig, adapter: await createPrismaAdapter({ ...adapter, client: prisma }) }`. The Nest server
70
+ still compiles its Contracts at startup and passes no `prebuilt`. `aventara:prepare` ends with `&& aventara-build`,
71
+ and `@aventara/build` is an exact development dependency.
72
+
73
+ - 2b1b17e: `aventara-build`'s files no longer land inside a Prisma client generated into `src/generated`.
74
+
75
+ When `aventara init` reuses a project whose Prisma client is generated into `src/generated`, it already puts the
76
+ discovery artifact in `src/aventara/`, because `prisma generate` deletes every file in its output directory that it did
77
+ not write. `aventara-build` still wrote its three files to `src/generated/aventara/`, so each `prisma generate` deleted
78
+ them and the project stopped compiling until the next `aventara:prepare`. Now all of Aventara's generated files are
79
+ in one place.
80
+
81
+ - `@aventara/build`: `aventara-build` writes `resources.generated.ts`, `computed-fields.generated.ts` and
82
+ `contracts.generated.ts` to the directory the configuration imports `aventaraResources` from, so the tree is always
83
+ written where the configuration reads it. A configuration that imports no tree keeps `src/generated/aventara/`.
84
+ The files of a project whose client is generated anywhere else are the same bytes, in the same place.
85
+ - `@aventara/prisma7-adapter`: with `--config`, the artifact imports the computed fields from its sibling,
86
+ `./computed-fields.generated`, wherever `--out` puts it. `--config` is still required for that import and the step
87
+ still checks that the file exists, but the step no longer uses its location. A scaffold's artifact is unchanged.
88
+ - `@aventara/cli`: in that project, `src/aventara.config.ts` imports the tree from `./aventara/resources.generated`,
89
+ and `.gitignore` gets the one `/src/aventara/` line that covers all four files (no separate
90
+ `/src/generated/aventara/contracts.generated.ts` line). The agent skill says where the generated files are in
91
+ both cases.
92
+
93
+ - 3813207: The Resource tree and the computed fields come from `aventara-build`.
94
+
95
+ - `@aventara/build`: before it compiles the Contracts, `aventara-build` writes two more files under
96
+ `src/generated/aventara/`. `resources.generated.ts` exports `aventaraResources`: every `defineResource(…)` result
97
+ that the files `src/resources/**/*.resource.ts` export, found by its type (never run), one `resources` tree per
98
+ layer, written as the plain spreads you wrote by hand (`root` always, `application`, `client` and `agent` when some
99
+ Resource has a block for them). Two definitions of one Resource stop the build, naming both. Imports are spelled as
100
+ the project spells its own. `computed-fields.generated.ts` exports `computedFields` and its type `AvComputedFields`:
101
+ which fields the configuration's write compute fills, read off its type after the tree exists, so a fresh install is
102
+ typed right on its first `aventara:prepare`. `--check` covers all three files. `typescript` (`>=5.7.0 <7`) is now a
103
+ peer dependency.
104
+ - `@aventara/prisma7-adapter`: the generate step no longer reads the configuration. With `--config`, the discovery
105
+ artifact imports `computedFields` and `AvComputedFields` from the file `aventara-build` writes beside the
106
+ configuration (`generated/aventara/computed-fields.generated.ts`); a `--config` file that does not exist is still
107
+ refused. `readConfigComputedFields`, `AvPrismaConfigReaderError` and `ConfigComputedFieldsReading` are removed from
108
+ `@aventara/prisma7-adapter/generate`, as are `PrismaGenerateToolchain.readComputedFields` and
109
+ `PrismaGenerateResult.computedFields`.
110
+ - `@aventara/cli`: the scaffold's `src/aventara.config.ts` takes its Resources from the tree,
111
+ `resources: aventaraResources.root`, so a new Resource file needs no configuration edit; the agent skill says so.
112
+
113
+ - Updated dependencies [e3d03a7]
114
+ - Updated dependencies [d10639a]
115
+ - Updated dependencies [f67e68c]
116
+ - Updated dependencies [708556c]
117
+ - Updated dependencies [3a0dfca]
118
+ - Updated dependencies [99169d2]
119
+ - Updated dependencies [0fd2608]
120
+ - Updated dependencies [154e440]
121
+ - Updated dependencies [2fcffdb]
122
+ - @aventara/core@1.0.0-rc.4
123
+
package/LICENSE ADDED
@@ -0,0 +1,91 @@
1
+ Required Notice: Copyright 2026 Mohamed Ragheb
2
+
3
+ # PolyForm Shield License 1.0.0
4
+
5
+ <https://polyformproject.org/licenses/shield/1.0.0>
6
+
7
+ ## Acceptance
8
+
9
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
14
+
15
+ ## Distribution License
16
+
17
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
18
+
19
+ ## Notices
20
+
21
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
22
+
23
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
24
+
25
+ ## Changes and New Works License
26
+
27
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
28
+
29
+ ## Patent License
30
+
31
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
32
+
33
+ ## Noncompete
34
+
35
+ Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
36
+
37
+ ## Competition
38
+
39
+ Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge. If you market a product as a practical substitute for the software or another product, it definitely competes.
40
+
41
+ ## New Products
42
+
43
+ If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
44
+
45
+ ## Discontinued Products
46
+
47
+ You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
48
+
49
+ > Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
50
+
51
+ ## Sales of Business
52
+
53
+ If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce [Noncompete](#noncompete) for that product.
54
+
55
+ ## Fair Use
56
+
57
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
58
+
59
+ ## No Other Rights
60
+
61
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
62
+
63
+ ## Patent Defense
64
+
65
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
66
+
67
+ ## Violations
68
+
69
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
70
+
71
+ ## No Liability
72
+
73
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
74
+
75
+ ## Definitions
76
+
77
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
78
+
79
+ A **product** can be a good or service, or a combination of them.
80
+
81
+ **You** refers to the individual or entity agreeing to these terms.
82
+
83
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
84
+
85
+ **Affiliates** means the other organizations than an organization has control over, is under the control of, or is under common control with.
86
+
87
+ **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
88
+
89
+ **Your licenses** are all the licenses granted to you for the software under these terms.
90
+
91
+ **Use** means anything you do with the software requiring one of your licenses.
@@ -0,0 +1,9 @@
1
+ # Additional Permission — Aventara
2
+
3
+ Copyright 2026 Mohamed Ragheb
4
+
5
+ In addition to the permissions of the PolyForm Shield License 1.0.0 in `LICENSE`, the licensor grants the following additional permission:
6
+
7
+ The Noncompete section applies only to providing a product that competes with the software itself — the Aventara framework, its packages and its tooling. Providing a product that competes with any other product the licensor or its affiliates provide using the software is a permitted purpose.
8
+
9
+ This additional permission only adds to your permissions; it does not restrict or replace any term of `LICENSE`.
package/README.md CHANGED
@@ -1,3 +1,139 @@
1
- # Temporary Holding Version
1
+ # `@aventara/build`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The `aventara-build` command compiles an Aventara application's Contracts when the project is built, so a serverless
4
+ or edge function loads them at a cold start instead of compiling them. It is a development dependency: nothing in your
5
+ deployed application imports it.
6
+
7
+ ```bash
8
+ npm install --save-dev --save-exact @aventara/build@<your @aventara/core version>
9
+ ```
10
+
11
+ `aventara new` and `aventara init` add it and run it from the project's `aventara:prepare` script, after the adapter's
12
+ generate step:
13
+
14
+ ```text
15
+ aventara-prisma7-generate … && aventara-build
16
+ ```
17
+
18
+ ## What it reads and writes
19
+
20
+ Run it from the project's root. It writes three files, in this order, each from what the one before wrote, so a fresh
21
+ install is right on its first `aventara:prepare`. They go to the directory your configuration imports
22
+ `aventaraResources` from, beside the discovery artifact: `src/generated/aventara/` normally, `src/aventara/` when
23
+ `aventara init` found Prisma's client generated into `src/generated` (which `prisma generate` would empty). A
24
+ configuration that imports no tree keeps `src/generated/aventara/`.
25
+
26
+ | File | What it holds |
27
+ |---|---|
28
+ | `resources.generated.ts` | `aventaraResources`: every Resource your `src/resources/**/*.resource.ts` files define, one `resources` tree per layer |
29
+ | `computed-fields.generated.ts` | `computedFields` (typed `AvComputedFields`): which fields the configuration's write compute fills, read off its type |
30
+ | `contracts.generated.ts` | `aventaraContracts` (typed `AvPrebuiltContracts`): the compiled Contracts |
31
+
32
+ ### The Resource tree
33
+
34
+ Each Resource's configuration lives in its own file under `src/resources/`, named `<name>.resource.ts`, as an exported
35
+ `defineResource(...)` result. The build reads those files through TypeScript (it never runs them), takes every export
36
+ whose type is a `defineResource` result, and writes the spreads you would otherwise write by hand:
37
+
38
+ ```ts
39
+ // src/generated/aventara/resources.generated.ts (generated)
40
+ import { post } from "../../resources/post.resource.js";
41
+ import { session } from "../../resources/session.resource.js";
42
+
43
+ export const aventaraResources = {
44
+ root: { ...post.root, ...session.root },
45
+ client: { ...post.client },
46
+ } as const;
47
+ ```
48
+
49
+ `root` is always there; `application`, `client` and `agent` only when some Resource gives that layer a block. The
50
+ configuration takes the trees once, and a new Resource file needs no edit to it:
51
+
52
+ ```ts
53
+ // src/aventara.config.ts
54
+ import { aventaraResources } from "./generated/aventara/resources.generated.js";
55
+
56
+ export const aventaraConfig = {
57
+ api: "/api",
58
+ exposed: true,
59
+ resources: aventaraResources.root,
60
+ client: { exposed: true, resources: aventaraResources.client },
61
+ } as const;
62
+ ```
63
+
64
+ Two files defining the same Resource stop the build, naming both: spread by hand, the later spread silently replaced
65
+ the earlier block. The imports are spelled as your project spells its own, by the rule `avclient` follows for the client it writes:
66
+ `.js` in an ES module under `nodenext`, none with `module: "commonjs"` or `moduleResolution: "bundler"`, `.ts` with
67
+ `allowImportingTsExtensions`. One difference: a CommonJS package under `nodenext` (Nest's CommonJS scaffold) gets no
68
+ suffix either, as the scaffold writes its own imports there, because these files import values and the jest such a
69
+ project tests with resolves no `.js` specifier to a `.ts` file. The tree is the same expression you would write, so it
70
+ costs the same type checking. The files matched are `src/resources/**/*.resource.ts`; the pattern is not configurable
71
+ yet.
72
+
73
+ ### The computed fields
74
+
75
+ The build reads which fields the configuration's `compute.create` and `compute.update` stages fill off the
76
+ configuration's TypeScript type, after it wrote the tree, and writes them to `computed-fields.generated.ts`. The Prisma
77
+ adapter's discovery artifact imports that file as its sibling (its generate step is given `--config src/aventara.config.ts`), so a
78
+ variant's guards, pipes and filters, which run before compute, may leave exactly those fields out of a create. Where the
79
+ configuration is not a literal (a wide `AvConfig` annotation, a conditional spread), the build prints where and the
80
+ file states nothing (`null`): those stages may then leave any create field out. A server warns
81
+ `AVENTARA_STALE_GENERATED_TYPES` at startup when the configuration computes other fields than the file says.
82
+
83
+ ### The Contracts
84
+
85
+ To compile the Contracts it runs two of your modules, in TypeScript or JavaScript, ES module or CommonJS:
86
+
87
+ | File | What it must export |
88
+ |---|---|
89
+ | `src/aventara.config.ts` | `aventaraConfig` (or a default export): the configuration as a plain value, without `adapter` |
90
+ | `src/aventara.adapter.ts` | a default export: the adapter's description, `describePrismaAdapter({ discovery, provider, driver })` for Prisma 7 |
91
+
92
+ It compiles the four Contracts with your project's own `@aventara/core`, from the configuration and the model the
93
+ description holds, and writes `src/generated/aventara/contracts.generated.ts`, whose export `aventaraContracts` is typed
94
+ `AvPrebuiltContracts`. It never constructs a database client and never connects to a database. The file is generated:
95
+ keep it out of git, as the scaffold's `.gitignore` does, and never edit it.
96
+
97
+ A configuration that a server would refuse at startup fails the build instead: every `CONFIG_…` and `COMPILER_…`
98
+ diagnostic is printed with its path and documentation link, nothing is written, and the command exits 1.
99
+
100
+ Because the build runs `src/aventara.config.ts`, everything it imports runs at build time too. Keep its top level free of
101
+ side effects (no connection, no request). A value read from `process.env` is read when the build runs.
102
+
103
+ ## `--check`
104
+
105
+ ```bash
106
+ aventara-build --check
107
+ ```
108
+
109
+ Writes nothing. It exits 0 when the three files on disk are the ones a build would write now, and 1 on the first that is
110
+ missing or stale, naming it (and, for the Contracts, the Resources whose configuration or model changed). Run it in CI
111
+ before a deploy.
112
+
113
+ ## Using the file
114
+
115
+ Pass `aventaraContracts` as `prebuilt` where the framework is created, beside the adapter:
116
+
117
+ ```ts
118
+ // a serverless or edge entry
119
+ import { createFramework } from "@aventara/core";
120
+ import { createPrismaAdapter } from "@aventara/prisma7-adapter";
121
+ import adapter from "./aventara.adapter.js";
122
+ import { aventaraConfig } from "./aventara.config.js";
123
+ import { aventaraContracts } from "./generated/aventara/contracts.generated.js";
124
+ import { prisma } from "./prisma.client.js";
125
+
126
+ export const framework = await createFramework({
127
+ ...aventaraConfig,
128
+ adapter: await createPrismaAdapter({ ...adapter, client: prisma }),
129
+ prebuilt: aventaraContracts,
130
+ });
131
+ ```
132
+
133
+ The framework uses the file only when it was compiled by the same `@aventara/core`, for the same protocol, from the same
134
+ configuration and model. Otherwise it ignores the whole file, compiles at startup as it would without one, and warns
135
+ once with `AVENTARA_PREBUILT_STALE`, naming what changed. A stale file never serves a wrong Contract; it only costs the
136
+ startup time the file was meant to save.
137
+
138
+ A long-running server does not need the file. The Nest project `aventara new` writes compiles its Contracts at startup
139
+ and passes no `prebuilt`.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { refuseUnsupportedNode } from "./node-version.guard.js";
3
+ if (!refuseUnsupportedNode("aventara-build", new URL("../package.json", import.meta.url))) {
4
+ void import("./cli.js").then((program) => program.runFromProcess());
5
+ }
@@ -0,0 +1,3 @@
1
+ export class AvBuildError extends Error {
2
+ name = "AvBuildError";
3
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,168 @@
1
+ import path from "node:path";
2
+ import { AvBuildError } from "./build.error.js";
3
+ import { readConfigComputedFields } from "./computed/computed-fields.reader.js";
4
+ import { emitComputedFieldsModule } from "./computed/computed-fields-file.js";
5
+ import { checkContractsFile, writeContractsFile, } from "./contracts/contracts-file.js";
6
+ import { compilePrebuiltContracts } from "./contracts/prebuilt-contracts.compiler.js";
7
+ import { readGeneratedFile, writeGeneratedFile, } from "./project/generated-file.js";
8
+ import { DEFAULT_GENERATED_FILES, resolveGeneratedFiles, } from "./project/generated-files.resolver.js";
9
+ import { findProjectModules, loadProjectModules, PROJECT_MODULES, } from "./project/project-modules.loader.js";
10
+ import { importSuffixOf, projectCompilerOptions, } from "./project/tsconfig.reader.js";
11
+ import { RESOURCES_GLOB, readResourceDefinitions, } from "./resources/resource-definitions.reader.js";
12
+ import { emitResourcesModule } from "./resources/resources-file.js";
13
+ const USAGE = `aventara-build — compile this project's Aventara Contracts ahead of time.
14
+
15
+ Usage:
16
+ aventara-build write the three files below
17
+ aventara-build --check write nothing; exit 1 when one is missing or stale
18
+
19
+ Run it from the project's root. It writes, in this order:
20
+
21
+ ${DEFAULT_GENERATED_FILES.resources}
22
+ \`aventaraResources\`: every \`defineResource(…)\` result the files
23
+ ${RESOURCES_GLOB} export, one \`resources\` tree per layer;
24
+ ${DEFAULT_GENERATED_FILES.computedFields}
25
+ which fields the configuration computes, read off its type;
26
+ ${DEFAULT_GENERATED_FILES.contracts}
27
+ the compiled Contracts, \`aventaraContracts\`.
28
+
29
+ All three go to the directory the configuration imports \`aventaraResources\`
30
+ from (src/aventara/ when \`aventara init\` found the ORM's client generated into
31
+ src/generated), else to src/generated/aventara/.
32
+
33
+ It runs ${PROJECT_MODULES.config}.ts (the configuration, its
34
+ \`aventaraConfig\` export or its default, without \`adapter\`) and
35
+ ${PROJECT_MODULES.adapter}.ts (its default export: the adapter's
36
+ description, such as \`describePrismaAdapter({ discovery, provider, driver })\`),
37
+ in TypeScript or JavaScript, and compiles the Contracts with the project's own
38
+ @aventara/core. It never constructs a database client.
39
+
40
+ A serverless or edge host passes the file's \`aventaraContracts\` to
41
+ createFramework as \`prebuilt\`; a long-running server needs none.
42
+
43
+ Options:
44
+ --check Exit 1, writing nothing, when a file is missing or stale.
45
+ -h, --help Print this and exit 0.`;
46
+ function parse(argv) {
47
+ let check = false;
48
+ for (const argument of argv) {
49
+ if (argument === "--check") {
50
+ check = true;
51
+ continue;
52
+ }
53
+ throw new AvBuildError(`unknown argument \`${argument}\`.\n\n${USAGE}`);
54
+ }
55
+ return { check };
56
+ }
57
+ function counted(count) {
58
+ return `${count} Resource${count === 1 ? "" : "s"}`;
59
+ }
60
+ function wrote(written, file, detail) {
61
+ return `aventara-build: ${written === "written" ? "wrote" : "unchanged:"} ${file}${detail === undefined ? "" : ` (${detail})`}.`;
62
+ }
63
+ function checked(projectDirectory, file, text, stale) {
64
+ const current = readGeneratedFile(projectDirectory, file);
65
+ return current === undefined
66
+ ? { file, what: "is missing" }
67
+ : current === text
68
+ ? undefined
69
+ : { file, what: `is stale: ${stale}` };
70
+ }
71
+ function refusal({ file, what, deploy }) {
72
+ return `aventara-build: ${file} ${what}. Run aventara-build (npm run aventara:prepare)${deploy === true ? " and deploy the file it writes" : ""}.`;
73
+ }
74
+ export async function runBuild(argv, projectDirectory, output) {
75
+ if (argv.includes("-h") || argv.includes("--help")) {
76
+ output.stdout(USAGE);
77
+ return 0;
78
+ }
79
+ try {
80
+ const { check } = parse(argv);
81
+ const files = findProjectModules(projectDirectory);
82
+ const generated = resolveGeneratedFiles(projectDirectory, files.configFile);
83
+ const options = projectCompilerOptions(projectDirectory);
84
+ const found = readResourceDefinitions(projectDirectory, options);
85
+ const tree = emitResourcesModule(projectDirectory, generated.resources, found.definitions, importSuffixOf(options, path.dirname(path.join(projectDirectory, generated.resources))));
86
+ for (const file of found.withoutDefinition) {
87
+ output.stdout(`aventara-build: ${file} exports no defineResource(…) result; it is not in aventaraResources.`);
88
+ }
89
+ if (check) {
90
+ const stale = checked(projectDirectory, generated.resources, tree, "the Resource files changed since it was written");
91
+ if (stale !== undefined) {
92
+ output.stderr(refusal(stale));
93
+ return 1;
94
+ }
95
+ }
96
+ else {
97
+ output.stdout(wrote(writeGeneratedFile(projectDirectory, generated.resources, tree), generated.resources, counted(found.definitions.length)));
98
+ }
99
+ if (check) {
100
+ if (readGeneratedFile(projectDirectory, generated.computedFields) ===
101
+ undefined) {
102
+ output.stderr(refusal({ file: generated.computedFields, what: "is missing" }));
103
+ return 1;
104
+ }
105
+ }
106
+ else if (readGeneratedFile(projectDirectory, generated.computedFields) ===
107
+ undefined) {
108
+ writeGeneratedFile(projectDirectory, generated.computedFields, emitComputedFieldsModule(files.configFile, {
109
+ kind: "unread",
110
+ reason: "the build has not read the configuration yet",
111
+ }));
112
+ }
113
+ const reading = readConfigComputedFields(path.join(projectDirectory, files.configFile), options, found.program);
114
+ const computed = emitComputedFieldsModule(files.configFile, reading);
115
+ if (check) {
116
+ const stale = checked(projectDirectory, generated.computedFields, computed, "the fields the configuration computes changed since it was written");
117
+ if (stale !== undefined) {
118
+ output.stderr(refusal(stale));
119
+ return 1;
120
+ }
121
+ }
122
+ else {
123
+ output.stdout(wrote(writeGeneratedFile(projectDirectory, generated.computedFields, computed), generated.computedFields));
124
+ if (reading.kind === "unread") {
125
+ output.stdout(`aventara-build: computed fields not read from ${files.configFile}: ${reading.reason}. The stages before compute may leave any create field out; write the configuration \`as const\` there.`);
126
+ }
127
+ }
128
+ const project = await loadProjectModules(projectDirectory, files);
129
+ const compiled = await compilePrebuiltContracts(project);
130
+ const resources = Object.keys(compiled.prebuilt.fingerprints.resources).length;
131
+ if (!check) {
132
+ output.stdout(wrote(writeContractsFile(projectDirectory, generated.contracts, compiled), generated.contracts, counted(resources)));
133
+ return 0;
134
+ }
135
+ const contracts = await checkContractsFile(projectDirectory, generated.contracts, compiled, project.internal.prebuiltFileChanges);
136
+ if (contracts.kind === "fresh") {
137
+ output.stdout(`aventara-build: ${generated.resources}, ${generated.computedFields} and ${generated.contracts} are up to date.`);
138
+ return 0;
139
+ }
140
+ output.stderr(refusal({
141
+ file: generated.contracts,
142
+ deploy: true,
143
+ what: contracts.kind === "missing"
144
+ ? "is missing"
145
+ : `is stale: ${contracts.changes.join("; ")}`,
146
+ }));
147
+ return 1;
148
+ }
149
+ catch (error) {
150
+ if (error instanceof AvBuildError) {
151
+ output.stderr(`aventara-build: ${error.message}`);
152
+ return 1;
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ export async function runFromProcess() {
158
+ try {
159
+ process.exitCode = await runBuild(process.argv.slice(2), process.cwd(), {
160
+ stdout: (line) => process.stdout.write(`${line}\n`),
161
+ stderr: (line) => process.stderr.write(`${line}\n`),
162
+ });
163
+ }
164
+ catch (error) {
165
+ process.stderr.write(`${String(error instanceof Error ? error.stack : error)}\n`);
166
+ process.exitCode = 1;
167
+ }
168
+ }
@@ -0,0 +1,37 @@
1
+ function literal(computedFields) {
2
+ const ordered = {
3
+ agent: computedFields.agent,
4
+ agentExtends: computedFields.agentExtends,
5
+ application: computedFields.application,
6
+ client: computedFields.client,
7
+ root: computedFields.root,
8
+ };
9
+ return JSON.stringify(ordered, null, "\t");
10
+ }
11
+ export function emitComputedFieldsModule(configFile, reading) {
12
+ const header = `// Generated by aventara-build from the type of ${configFile}. Do not edit: regenerate it (aventara:prepare).`;
13
+ const type = [
14
+ "",
15
+ "// What an adapter's discovery artifact carries on its model's type for the stages that run before compute.",
16
+ "export type AvComputedFields = typeof computedFields;",
17
+ ];
18
+ if (reading.kind === "unread") {
19
+ return `${[
20
+ header,
21
+ "",
22
+ `// Not read: ${reading.reason.replaceAll("\n", " ")}. The stages that run before compute may leave any`,
23
+ "// create field out; write the configuration `as const` there to have it read.",
24
+ "export const computedFields = null;",
25
+ ...type,
26
+ ].join("\n")}\n`;
27
+ }
28
+ return `${[
29
+ header,
30
+ 'import type { AvConfigComputedFields } from "@aventara/core";',
31
+ "",
32
+ "// Which fields the configuration's write compute fills (`compute.create`, `compute.update`), per layer and",
33
+ "// Resource. The framework compares it with the configuration at startup and warns when they differ.",
34
+ `export const computedFields = ${literal(reading.computedFields)} as const satisfies AvConfigComputedFields;`,
35
+ ...type,
36
+ ].join("\n")}\n`;
37
+ }