@nocobase/app-cli-distribution 0.0.0-stage → 0.1.0-beta.0

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/LICENSE.txt ADDED
@@ -0,0 +1,129 @@
1
+ Updated Date: November 7, 2026
2
+
3
+ NocoBase License Agreement
4
+
5
+ NOCOBASE PTE. LTD., a Singaporean Exempt Private Company Limited by Shares with its principal place of business located at 112 ROBINSON ROAD, #03-01, SINGAPORE ("The Company") https://www.nocobase.com/ issues this License Agreement ("Agreement") to you. You, as an individual or a company ("The User"), will be deemed to voluntarily accept all terms of this Agreement by using NocoBase (including but not limited to obtaining NocoBase source code, installation package, or image in any form, installing and using NocoBase, developing Applications with NocoBase, purchasing NocoBase commercial license and services). If the User does not agree to any term of this Agreement, or cannot accurately understand our interpretation of the relevant terms, please stop using it immediately.
6
+
7
+ This Agreement applies to any use, quotation, contract, invoice, and all software delivered by the Company. The User and the Company or NocoBase's agents need not sign a separate license agreement for the sale and delivery of the software.
8
+
9
+ This Agreement applies to NocoBase 3 and later versions. NocoBase 2 and earlier versions are independent products and remain subject to their original license agreement. Where a NocoBase 2 Commercial Edition user uses NocoBase 3 or later versions under a policy announced by the Company, that use is subject to this Agreement.
10
+
11
+ The Company reserves the right to formulate and modify this Agreement from time to time as needed. If there are changes, the Company will announce them in the form of website announcements, without further individual notification. The changed Agreement will automatically take effect once it is announced, becoming part of this Agreement.
12
+
13
+ ==============
14
+ 1. Definitions
15
+ ==============
16
+
17
+ 1.1 "Software" refers to NocoBase, consisting of Core, Studio, and Commercial Plugins, including their source code, installation packages, images, and all their modifications, updates, and upgrades.
18
+
19
+ 1.2 "Core" refers to NocoBase Core, the open source application foundation released by the Company, including the runtime, plugins, application templates, development standards, and development skills placed in the same code repository.
20
+
21
+ 1.3 "Studio" refers to NocoBase Studio, the governance platform for people and agents building software together, provided in the Commercial Edition.
22
+
23
+ 1.4 "Commercial Plugins" refers to the plugins provided in the Commercial Edition that extend the capabilities of Core, such as advanced approvals and enterprise SSO.
24
+
25
+ 1.5 "Community Edition" refers to the free version of the Software provided to the User through public channels, namely Core.
26
+
27
+ 1.6 "Commercial Edition" refers to the paid version of the Software purchased by the User from the Company or its agents, downloaded through exclusive channels, and includes additional benefits. In addition to Core, it includes Studio and Commercial Plugins, and consists of two versions: Professional Edition and Enterprise Edition.
28
+
29
+ 1.7 "Application" refers to a specific business use case application serving internal or external customers of the User, developed based on the Software, such as ERP/CRM. Studio is a development tool and is not part of any Application.
30
+
31
+ 1.8 "Customer" refers to the clients who commission the User to develop Applications or purchase the User's Applications.
32
+
33
+ 1.9 "Agent" refers to an AI agent that works with the Software, including the coding agents the User uses to develop Applications and the business agents in Applications.
34
+
35
+ 1.10 "Third-Party Services" refers to the large models, coding agents, cloud services, and other third-party products and services that the User chooses to use with the Software.
36
+
37
+ 1.11 "Third-Party Open Source Software" refers to open source software provided with the Software. They are licensed through various published open source software licenses or copyright notices accompanying such software.
38
+
39
+ ===================================
40
+ 2. Intellectual Property Protection
41
+ ===================================
42
+
43
+ Except for Third-Party Open Source Software, the Company owns all copyrights, trademark rights, patent rights, trade secrets, and other intellectual property rights of the Software, and has registered and protected them in relevant countries and regions according to the "Paris Convention" or "TRIPS Agreement", ensuring that the intellectual property rights of the Software are internationally recognized and protected.
44
+
45
+ The Company claims no intellectual property rights in the business code written by the User or by Agents when developing Applications, or in the User's business data, skills, and knowledge bases. The parts of the Software contained in an Application remain the intellectual property of the Company and are subject to this Agreement.
46
+
47
+ =============
48
+ 3. Disclaimer
49
+ =============
50
+
51
+ 3.1 The User shall not use the Software to engage in activities that contravene applicable laws and regulations or offend against public order or religious prohibitions. All legal liabilities and consequences arising from the User's use shall be borne by the User.
52
+
53
+ 3.2 The Company shall not be liable for any direct, indirect, special, incidental, or consequential damages (including but not limited to loss of profits, business interruption, data loss, or business information disclosure) caused by the User's use of the Software, even if it has been previously informed of the possibility of such damages.
54
+
55
+ 3.3 Code written, content generated, and actions taken by Agents shall be reviewed and accepted by the User before being used in production. All consequences arising from the use of the work of Agents shall be borne by the User.
56
+
57
+ 3.4 Third-Party Services are chosen and paid for by the User and are subject to their own terms. The Company shall not be liable for the availability, output, or data handling of Third-Party Services.
58
+
59
+ ===============
60
+ 4. License Type
61
+ ===============
62
+
63
+ 4.1 This Agreement serves as the unified license agreement for NocoBase Software, applying to both the Community Edition and the Commercial Editions.
64
+
65
+ 4.2 Core is open source software. This Agreement incorporates and references the full text of the Apache License, Version 2.0 ("Apache-2.0", available at: https://www.apache.org/licenses/LICENSE-2.0 ). Users of Core must comply with the Apache-2.0 License as well as the supplementary terms set forth in this Agreement. In case of any inconsistency between Apache-2.0 and this Agreement, the supplementary terms of this Agreement shall prevail.
66
+
67
+ 4.3 Studio and Commercial Plugins are commercial software of the Company and are not licensed under Apache-2.0. They can only be used under the Commercial License and in accordance with this Agreement.
68
+
69
+ ================================================
70
+ 5. Rights and Obligations of Open Source License
71
+ ================================================
72
+
73
+ 5.1 The Community Edition can be used for commercial purposes, including developing Applications for Customers.
74
+
75
+ 5.2 It is not allowed to remove or change the NocoBase statement "AI + NocoBase" and "Build reliable business systems", or its link to https://www.nocobase.com/, at the bottom left of the interface of any Application, including Applications delivered to Customers, even though the code that displays the statement is part of the Application. If the default layout is replaced, the statement shall be kept at an equally visible position on the main interface of the Application. Pages that can be accessed without signing in and pages embedded in other systems are not subject to this requirement. When the sidebar is collapsed, showing the statement as an icon with a tooltip, as provided by default, meets this requirement. The main LOGO in the upper left corner of the page, the version number, and other information on the interface can be changed.
76
+
77
+ 5.3 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
78
+
79
+ 5.4 It is not allowed to provide to the public any form of AI application development platform or agent development platform SaaS/PaaS products using the original or modified Software.
80
+
81
+ ===============================
82
+ 6. Rights of Commercial License
83
+ ===============================
84
+
85
+ 6.1 Obtain a permanent commercial license of the Software.
86
+
87
+ 6.2 Can develop Applications for Customers, deploy the completed Applications, including the Commercial Plugins they use, on Customers' servers, or host them and provide them to Customers as a service. This does not include Studio.
88
+
89
+ 6.3 Customers can use the Commercial Plugins in the Applications delivered to them, within those Applications only, and can continue to develop those Applications on their own or through others.
90
+
91
+ 6.4 Get software upgrades and exclusive technical support during the upgrade validity period. Upgrades and technical support are provided to the User only, and the User can upgrade the Commercial Plugins in Applications delivered to Customers during this period.
92
+
93
+ 6.5 The licensed Software can be used for commercial purposes with no restrictions on the number of Applications, Customers, users, and Agents.
94
+
95
+ 6.6 Can remove or change the NocoBase statement described in 5.2, and the brand, name, link, and other information about NocoBase on the interface of Applications.
96
+
97
+ 6.7 If there are other agreements in the contract for the above rights, the contract agreement shall prevail.
98
+
99
+ ====================================
100
+ 7. Obligations of Commercial License
101
+ ====================================
102
+
103
+ 7.1 It is not allowed to remove or change all intellectual property statements about NocoBase in the code.
104
+
105
+ 7.2 It is not allowed to sell, transfer, lease, share, gift, or distribute the Commercial License. Deploying Applications for Customers under 6.2 is not considered sharing the Commercial License, but the license key, access token, or other credentials of the Commercial License shall not be provided to Customers for any purpose other than deploying and running the Applications delivered to them.
106
+
107
+ 7.3 It is not allowed to sell, transfer, lease, share, or distribute any form of AI application development platform, agent development platform, or developer tools developed based on Software.
108
+
109
+ 7.4 It is not allowed to provide any form of AI application development platform or agent development platform SaaS/PaaS products to the public using the original or modified Software.
110
+
111
+ 7.5 Studio can only be used by the User holding the Commercial License, on servers owned or rented by the User. It is not allowed to deploy Studio for Customers, or to provide Studio to Customers or any third party in any form, including as a hosted service. Customers that need Studio shall purchase their own Commercial License.
112
+
113
+ 7.6 Customer personnel can take part in requirement confirmation, acceptance, and release approval in the User's Studio for the Applications the User develops for them. This is not considered providing Studio to Customers, but it is not allowed to let Customers use Studio to develop their own projects.
114
+
115
+ 7.7 If there is a violation of the above obligations or the terms of this Agreement, the rights owned by the User will be immediately terminated, the paid fees will not be refunded, and the Company reserves the right to pursue the User's legal responsibility.
116
+
117
+ 7.8 If there are other agreements in the contract for the above obligations, the contract agreement shall prevail.
118
+
119
+ =============================================================
120
+ 8. Legal Jurisdiction, Interpretation, and Dispute Resolution
121
+ =============================================================
122
+
123
+ 8.1 Except for Mainland China, the interpretation, application, and all matters related to this agreement are subject to the jurisdiction of Singapore law.
124
+
125
+ 8.2 Any dispute related to this Agreement should first be resolved through friendly negotiation. If the negotiation fails to resolve the dispute, the dispute should be submitted to the International Chamber of Commerce (ICC) for arbitration. The arbitration venue should be Singapore, conducted in English.
126
+
127
+ 8.3 All terms and conditions of this Agreement shall be deemed enforceable to the maximum extent permitted by applicable law. If any term of this Agreement is deemed invalid by any applicable law, the invalidity of that term does not affect the validity of any other term of this Agreement, and it should be deemed that the invalid term has been modified as much as possible to make it valid and enforceable, or if the term cannot be modified, it should be deemed to have been deleted from this Agreement.
128
+
129
+ 8.4 The arbitration award is final, binding on both parties, and can be enforced in any court with jurisdiction.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # @nocobase/app-cli-distribution
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
+ Server-side artifact distribution and CLI installation without registering Agents. The package has no database, migrations, jobs, Runner service, registration flow or frontend. Agents uses the same implementation with its original credentials, table and HTTP contract.
4
+
5
+ ## Mounting a CLI-only host
6
+
7
+ Create one artifact store per host configuration so its manifest cache is retained. Mount both routers at `/api/agents/dist` (below the application's base path): existing installed CLIs use these paths for `update`. Build artifacts with `nocobase cli build --universal --out <artifact-directory>`, or platform-specific targets. The layout is `<directory>/<channel>/<product>/manifest.json` and the files that manifest lists.
8
+
9
+ ```ts
10
+ import { Hono } from 'hono';
11
+ import {
12
+ createArtifactStore,
13
+ createDistributionRoutes,
14
+ createInstallRoutes,
15
+ } from '@nocobase/app-cli-distribution';
16
+
17
+ const artifacts = createArtifactStore({ dir: artifactDirectory });
18
+ const distribution = new Hono()
19
+ .route(
20
+ '/',
21
+ createDistributionRoutes({
22
+ artifacts,
23
+ products: ['acme'],
24
+ // Supply the host's real authentication middleware. The host decides
25
+ // whether a session, API key or another credential may download.
26
+ fallback: requireSignedInPerson,
27
+ }),
28
+ )
29
+ .route(
30
+ '/',
31
+ createInstallRoutes({
32
+ cli: () => 'acme',
33
+ publicOrigin: () => publicOrigin,
34
+ publicBasePath: '/main',
35
+ }),
36
+ );
37
+ app.route('/api/agents/dist', distribution);
38
+ ```
39
+
40
+ `app` is the host's Hono API router. A NocoBase host contributes this router through its normal server routes declaration; it does not register the Agents plugin. `products` restricts manifest entries, resolution and file requests, including npm resolution. It is required: pass the CLI allowlist, or explicitly pass `'all'` to serve every configured product as Agents does. Omitting it throws when the router is created. A request for another product receives 404 after authorization. The artifact directory and manifests are trusted deployment inputs; isolate them from user uploads and untrusted filesystem writers. Files cannot escape their product directory through a manifest path.
41
+
42
+ ## Authorization is explicit
43
+
44
+ No artifact is public by default. `admissions` are ordered asynchronous handlers: return `false` when the handler recognizes no credential, return `true` after authorizing it, and throw on an invalid recognized credential. An error never falls through to another handler or a signed-in session. After every handler declines, `fallback` may authorize through the host. With neither an accepting handler nor a fallback, requests receive 401 `UNAUTHORIZED` in the existing `agents` error domain. Never use download credentials as business login credentials.
45
+
46
+ The package implements no public-download strategy or signed credential protocol. A host can write its own authorization middleware, which makes its security policy the host's responsibility. Choosing Main's policy, any public access, and any new persistent token store require a separate product decision. This package does not select one or create a table.
47
+
48
+ `createInstallRoutes` serves a public script containing no credential. The CLI-only script accepts `--token`, `--server`, `--prefix`, `--bin-dir`, `--dry-run`, `-h` and `--help`; it sends the token in `x-nocobase-download-token`. A session-only host can resolve/download/update through its authenticated clients, but must supply an installation credential admission if it wants this token-based one-line installation. The `InstallScriptExtensions.credentials` hook may implement another host-approved installation transport. No Runner flags, registration or services are in the default script.
49
+
50
+ ## Optional stateful download tokens
51
+
52
+ `createDownloadTokenService({ product, store, clock, ids })` implements the existing algorithm: 32 random bytes with the protocol prefix, SHA-256-only storage, 30 minutes, three tarball downloads, and the first requested platform binding. Pass its `downloadTokenAdmission(tokens, artifacts)` in `admissions`. Only when `mint: { guard, tokens, createdBy }` is supplied does the router expose `POST /downloadTokens`; the guard must authenticate and authorize the creator, and `createdBy(context)` supplies its user id. Agents keeps its existing person guard, including refusal of scoped API keys.
53
+
54
+ `DownloadTokenStore.transaction(fn)` supplies `insert(record)`, `findByHash(hash)` and `updateIfDownloads(id, expected, values)` on one host-owned transaction. Reads must return snapshots; the update must atomically match both id and expected downloads and return whether exactly one row changed. Roll back when the callback throws. Preserve null target values and string timestamps. The in-memory adapter under `tests/` is test-only and does not promise durable storage or production transaction isolation.
55
+
56
+ Agents adapts the unchanged `agDownloadTokens` repository and its existing transaction runner. No table or historical migration moves, and live pre-upgrade hashes remain usable. Its file authorization still checks token validity before lookup, then binds/counts the actual platform, using two admissions. Failed or cancelled transfers consume a download credit as before.
57
+
58
+ ## Artifact and script contracts
59
+
60
+ The artifact store selects the highest version or an explicitly pinned version, uses a platform tarball before a universal tarball, and falls back to configured npm packages only when a caller accepts npm and no current artifact exists. Only manifest-listed files are sent, with stat-derived length, SHA-256 metadata and the existing cache headers. Manifest parsing is cached by modification time. File bodies use `createReadStream` and `Readable.toWeb`; cancellation destroys the underlying stream, without buffering the archive.
61
+
62
+ The default script verifies SHA-256, installs platform/universal/npm packages, writes `install.json` with `mode: cli`, switches `current`, restarts saved ACP connections and prints PATH/sign-in guidance. It retains `NOCOBASE_CLI_INSTALL_DIR`, `NOCOBASE_CLI_BIN_DIR`, `NOCOBASE_CLI_TARGET` and `NOCOBASE_NPM`. `requestedServer` preserves public origin, forwarded origin and base-path handling and refuses unsafe shell defaults.
63
+
64
+ `InstallScriptExtensions` contains trusted shell fragments for usage, initialization, argument parsing, validation, credential transport, preparation, resolution failures, and post-install actions. These are executable host code, never HTTP input or user configuration. Agents supplies its Runner-specific fragments through this interface, while sharing all archive selection, checksum, npm and installation logic. `openapi` overrides on both route factories let a host describe its actual authentication policy; Agents supplies its unchanged operation ids, tags, descriptions and security declarations.
65
+
66
+ ## Compatibility and verification
67
+
68
+ | Existing Agents endpoint | Preserved behavior |
69
+ | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `POST /downloadTokens` | Person guard; scoped-key refusal; 201 `{ data: { token, expiresAt, maxDownloads } }`; hidden from CLI discovery |
71
+ | `GET /installScript` | Public; shell content type; no-cache; all existing CLI and Runner flags, environment variables, errors and request behavior |
72
+ | `GET /manifest` | Runner key without touch, download token, unused registration token, then session/API-key fallback; original error reasons |
73
+ | `GET /products/:product/targets/:target` | `format=env`, `accept=npm`, universal fallback, unsupported-platform details, original 404 reasons, no-store |
74
+ | `GET /products/:product/versions/:version/files/:file` | Authorization before file existence, platform binding and credit counting; gzip stream, length, checksum, immutable private cache |
75
+
76
+ The new package's host tests exercise protected defaults, an explicit session policy, an in-memory token policy, product restrictions, invalid manifests, missing/unlisted/escaping files and stream cancellation without Agents. Agents' unchanged distribution/install/client/work/OpenAPI tests cover its adapter; the upgrade suite inserts old-shaped records directly and checks expiry, first platform, limits and concurrent final-credit consumption. Real installation/update and RSS measurements are local checks reported with the change, not CI cases.
77
+
78
+ The extraction adds no database query, write, lock or transaction to an Agents request and no polling or Runner claim work. Token mint remains one insert/transaction; metadata admission one read/transaction plus a conditional update when binding; a token file request remains two reads/transactions and one conditional counting update. The product restriction adds an in-memory membership check. No independent-host database or jobs are required.
79
+
80
+ Main integration and UI, its authorization choice, signed tokens/public downloads, token cleanup, mode switching between independent distribution and Agents, and the client's full-buffer update download remain separate work. One router owns `/api/agents/dist` in a host; do not register both independent distribution and Agents at that prefix.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Distribution: the runner (`nocobase-runner`) and the application's CLI (such as `acme`), served by the application
3
+ * itself as tarballs, so nothing is published to a registry: one per target, each bundling Node, or one for every
4
+ * target (`UNIVERSAL_TARGET`, `nocobase cli build --universal`) that runs on the machine's own Node.js 24 or newer.
5
+ *
6
+ * `nocobase cli build` of `@nocobase/app-cli` (`--runner` for the runner) builds each product into a directory of its
7
+ * own, with its own manifest, so the products are built and mounted apart (in CI, one artifact each):
8
+ *
9
+ * <dir>/<channel>/<product>/manifest.json
10
+ * <dir>/<channel>/<product>/<version>/<product>-v<version>-<target>.tar.gz
11
+ *
12
+ * where a product's manifest lists its versions with each target's file (relative to the product's directory),
13
+ * SHA-256 and size. A target without a file of its own gets the version's universal one, if it has one, answered with
14
+ * `universal: true`; a file built for the target wins over it. The application serves one channel (`agents.dist.channel`, `stable` by default); a product's
15
+ * current version is the one `agents.dist.versions` pins, or the highest the channel has. Only files a manifest lists
16
+ * are ever served.
17
+ *
18
+ * A product the directory has no current version of may instead be named on the npm registry (`agents.dist.npm`: its
19
+ * package and exact version), answered only to callers that understand it (`npmPackage`). The directory always wins.
20
+ */
21
+ import { type ReadStream } from 'node:fs';
22
+ import { type DistArtifact, type DistManifest, type DistNpmPackage } from '@nocobase/agent-protocol';
23
+ /** `agents.dist` in the application's configuration. */
24
+ export interface DistConfig {
25
+ /** Where the built tarballs are; `storage/runners/dist` of the application by default. */
26
+ readonly dir?: string;
27
+ /** Which channel to serve: `stable` by default. */
28
+ readonly channel?: string;
29
+ /** A version to serve per product instead of the channel's highest, such as `{ acme: '0.3.1' }`. */
30
+ readonly versions?: Readonly<Record<string, string>>;
31
+ /**
32
+ * The npm package and exact version each product is published as, such as
33
+ * `{ 'nocobase-runner': { package: '@nocobase/agent-runner', version: '1.0.0' } }`: answered instead of a tarball
34
+ * only when `dir` has no current version of the product, and only to callers that understand it (`DistNpmPackage`).
35
+ */
36
+ readonly npm?: Readonly<Record<string, DistNpmSource>>;
37
+ }
38
+ /** One product of `DistConfig.npm`. */
39
+ export interface DistNpmSource {
40
+ readonly package: string;
41
+ /** An exact version, never a range. */
42
+ readonly version: string;
43
+ }
44
+ export declare const DEFAULT_CHANNEL = "stable";
45
+ /** A file the application may serve, found through the manifest. */
46
+ export interface DistFile {
47
+ /** The platform it is built for: `UNIVERSAL_TARGET` for the one that serves every platform. */
48
+ readonly target: string;
49
+ readonly path: string;
50
+ readonly sha256: string;
51
+ readonly size: number;
52
+ open(): ReadStream;
53
+ }
54
+ export interface DistService {
55
+ readonly channel: string;
56
+ /** What the channel serves now; empty when nothing was built. */
57
+ manifest(): Promise<DistManifest>;
58
+ /** The current `product` for `target`, or null. */
59
+ find(product: string, target: string): Promise<DistArtifact | null>;
60
+ /** Like `find`, but 404: `PLATFORM_UNSUPPORTED` with the targets there are, or `NOT_FOUND` for no such product. */
61
+ resolve(product: string, target: string): Promise<DistArtifact>;
62
+ /** A file the manifest lists (any version on the channel); 404 otherwise. */
63
+ file(product: string, version: string, name: string): Promise<DistFile>;
64
+ /**
65
+ * The npm package to install `product` from (`DistConfig.npm`), or null when none is configured or the directory has
66
+ * a current version of the product, which is then served as a tarball exactly as without it. Only for callers that
67
+ * understand the answer: the resolve route's `accept=npm`, a runner with the `npm` feature.
68
+ */
69
+ npmPackage(product: string): Promise<DistNpmPackage | null>;
70
+ }
71
+ export declare function createArtifactStore(config?: DistConfig): DistService;
72
+ //# sourceMappingURL=artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAI5D,OAAO,EASL,KAAK,YAAY,EACjB,KAAK,YAAY,EAEjB,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAUlC,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,0FAA0F;IAC1F,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;CACxD;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAUD,eAAO,MAAM,eAAe,WAAW,CAAC;AA6BxC,oEAAoE;AACpE,MAAM,WAAW,QAAQ;IACvB,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,IAAI,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAClC,mDAAmD;IACnD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACpE,mHAAmH;IACnH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChE,6EAA6E;IAC7E,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxE;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CAC7D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,UAAe,GAAG,WAAW,CAgNxE"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Distribution: the runner (`nocobase-runner`) and the application's CLI (such as `acme`), served by the application
3
+ * itself as tarballs, so nothing is published to a registry: one per target, each bundling Node, or one for every
4
+ * target (`UNIVERSAL_TARGET`, `nocobase cli build --universal`) that runs on the machine's own Node.js 24 or newer.
5
+ *
6
+ * `nocobase cli build` of `@nocobase/app-cli` (`--runner` for the runner) builds each product into a directory of its
7
+ * own, with its own manifest, so the products are built and mounted apart (in CI, one artifact each):
8
+ *
9
+ * <dir>/<channel>/<product>/manifest.json
10
+ * <dir>/<channel>/<product>/<version>/<product>-v<version>-<target>.tar.gz
11
+ *
12
+ * where a product's manifest lists its versions with each target's file (relative to the product's directory),
13
+ * SHA-256 and size. A target without a file of its own gets the version's universal one, if it has one, answered with
14
+ * `universal: true`; a file built for the target wins over it. The application serves one channel (`agents.dist.channel`, `stable` by default); a product's
15
+ * current version is the one `agents.dist.versions` pins, or the highest the channel has. Only files a manifest lists
16
+ * are ever served.
17
+ *
18
+ * A product the directory has no current version of may instead be named on the npm registry (`agents.dist.npm`: its
19
+ * package and exact version), answered only to callers that understand it (`npmPackage`). The directory always wins.
20
+ */
21
+ import { createReadStream } from 'node:fs';
22
+ import { readdir, readFile, stat } from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { compareVersions, DIST_PRODUCT_PATTERN, DIST_ROUTES, DIST_TARGET_PATTERN, EXACT_VERSION_PATTERN, NPM_PACKAGE_PATTERN, ProtocolError, routePath, UNIVERSAL_TARGET, } from '@nocobase/agent-protocol';
25
+ import { z } from 'zod';
26
+ function notFound(what) {
27
+ return new ProtocolError(what === 'Product' ? 'PRODUCT_NOT_FOUND' : 'DIST_FILE_NOT_FOUND', `${what} not found.`);
28
+ }
29
+ const DistNpmConfigSchema = z.record(z.string().regex(DIST_PRODUCT_PATTERN), z.object({
30
+ package: z.string().max(214).regex(NPM_PACKAGE_PATTERN),
31
+ version: z.string().max(64).regex(EXACT_VERSION_PATTERN),
32
+ }));
33
+ export const DEFAULT_CHANNEL = 'stable';
34
+ const StoredTargetSchema = z.object({
35
+ file: z.string().min(1),
36
+ sha256: z.string().regex(/^[0-9a-f]{64}$/u),
37
+ size: z.number().int().nonnegative(),
38
+ });
39
+ const StoredVersionsSchema = z.record(z.string(), z.object({ targets: z.record(z.string(), StoredTargetSchema) }));
40
+ /** The manifest of one product, `<channel>/<product>/manifest.json`, with files relative to the product's directory. */
41
+ const ProductManifestSchema = z.object({
42
+ schema: z.literal(1),
43
+ product: z.string().regex(DIST_PRODUCT_PATTERN),
44
+ bin: z.string().optional(),
45
+ versions: StoredVersionsSchema,
46
+ });
47
+ export function createArtifactStore(config = {}) {
48
+ const channel = config.channel ?? DEFAULT_CHANNEL;
49
+ const npmSources = parseNpmSources(config.npm);
50
+ const channelDir = config.dir === undefined ? null : path.resolve(config.dir, channel);
51
+ let cache;
52
+ /** Each product's manifest file with its modification time, or nothing when there is none. */
53
+ const manifestFiles = async () => {
54
+ if (channelDir === null)
55
+ return [];
56
+ const found = [];
57
+ let entries;
58
+ try {
59
+ entries = (await readdir(channelDir)).sort();
60
+ }
61
+ catch {
62
+ return found;
63
+ }
64
+ for (const product of entries) {
65
+ if (!DIST_PRODUCT_PATTERN.test(product))
66
+ continue;
67
+ const file = path.join(channelDir, product, 'manifest.json');
68
+ try {
69
+ found.push({ file, product, mtimeMs: (await stat(file)).mtimeMs });
70
+ }
71
+ catch {
72
+ // A product not built yet.
73
+ }
74
+ }
75
+ return found;
76
+ };
77
+ const readManifest = async (file) => {
78
+ try {
79
+ return JSON.parse(await readFile(file, 'utf8'));
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ };
85
+ const load = async () => {
86
+ const files = await manifestFiles();
87
+ if (files.length === 0) {
88
+ cache = undefined;
89
+ return null;
90
+ }
91
+ const key = files
92
+ .map((entry) => `${entry.file}:${entry.mtimeMs}`)
93
+ .join('|');
94
+ if (cache?.key === key)
95
+ return cache.manifest;
96
+ const merged = { products: {} };
97
+ for (const entry of files) {
98
+ const raw = await readManifest(entry.file);
99
+ const product = entry.product;
100
+ const parsed = ProductManifestSchema.safeParse(raw);
101
+ if (!parsed.success || parsed.data.product !== product) {
102
+ console.error(`Agents: ${entry.file} is not the distribution manifest of ${product}.`);
103
+ continue;
104
+ }
105
+ // Its files are relative to the product's directory; the merged ones are relative to the channel's.
106
+ const versions = {};
107
+ for (const [version, stored] of Object.entries(parsed.data.versions))
108
+ versions[version] = {
109
+ targets: Object.fromEntries(Object.entries(stored.targets).map(([target, file]) => [
110
+ target,
111
+ { ...file, file: path.posix.join(product, file.file) },
112
+ ])),
113
+ };
114
+ merged.products[product] = {
115
+ ...(parsed.data.bin === undefined ? {} : { bin: parsed.data.bin }),
116
+ versions,
117
+ };
118
+ }
119
+ cache = { key, manifest: merged };
120
+ return merged;
121
+ };
122
+ const currentVersion = (stored, product) => {
123
+ const versions = Object.keys(stored.products[product]?.versions ?? {});
124
+ const pinned = config.versions?.[product];
125
+ if (pinned !== undefined)
126
+ return versions.includes(pinned) ? pinned : null;
127
+ return versions.sort(compareVersions).at(-1) ?? null;
128
+ };
129
+ const artifact = (stored, product, target) => {
130
+ const version = currentVersion(stored, product);
131
+ if (version === null)
132
+ return null;
133
+ const targets = stored.products[product]?.versions[version]?.targets;
134
+ const own = targets?.[target];
135
+ const entry = own ?? targets?.[UNIVERSAL_TARGET];
136
+ if (entry === undefined)
137
+ return null;
138
+ return {
139
+ product,
140
+ version,
141
+ target,
142
+ url: routePath(DIST_ROUTES.file, {
143
+ product,
144
+ version,
145
+ file: path.posix.basename(entry.file),
146
+ }),
147
+ sha256: entry.sha256,
148
+ size: entry.size,
149
+ channel,
150
+ ...(own === undefined ? { universal: true } : {}),
151
+ };
152
+ };
153
+ return {
154
+ channel,
155
+ async manifest() {
156
+ const stored = await load();
157
+ const products = {};
158
+ for (const product of Object.keys(stored?.products ?? {})) {
159
+ const version = stored && currentVersion(stored, product);
160
+ if (!stored || version === null)
161
+ continue;
162
+ products[product] = {
163
+ version,
164
+ targets: Object.keys(stored.products[product]?.versions[version]?.targets ?? {}).sort(),
165
+ };
166
+ }
167
+ return { channel, products };
168
+ },
169
+ async find(product, target) {
170
+ const stored = await load();
171
+ return stored ? artifact(stored, product, target) : null;
172
+ },
173
+ async resolve(product, target) {
174
+ if (!DIST_PRODUCT_PATTERN.test(product))
175
+ throw notFound('Product');
176
+ if (!DIST_TARGET_PATTERN.test(target))
177
+ throw new ProtocolError('PLATFORM_UNSUPPORTED', `${target} is not a platform name such as darwin-arm64 or linux-x64.`);
178
+ const stored = await load();
179
+ const found = stored ? artifact(stored, product, target) : null;
180
+ if (found)
181
+ return found;
182
+ const version = stored ? currentVersion(stored, product) : null;
183
+ if (!stored || version === null)
184
+ throw new ProtocolError('NOT_FOUND', `This application serves no ${product} on the ${channel} channel. Build it with nocobase cli build.`);
185
+ const targets = Object.keys(stored.products[product]?.versions[version]?.targets ?? {}).sort();
186
+ throw new ProtocolError('PLATFORM_UNSUPPORTED', `${product} ${version} is not built for ${target}; it is for ${targets.join(', ') || 'no platform'}.`, { targets });
187
+ },
188
+ async file(product, version, name) {
189
+ const stored = await load();
190
+ const targets = stored?.products[product]?.versions[version]?.targets;
191
+ const found = Object.entries(targets ?? {}).find(([, candidate]) => path.posix.basename(candidate.file) === name);
192
+ if (!found || channelDir === null)
193
+ throw notFound('File');
194
+ const [target, entry] = found;
195
+ const file = path.resolve(channelDir, entry.file);
196
+ if (!file.startsWith(`${path.resolve(channelDir, product)}${path.sep}`))
197
+ throw notFound('File');
198
+ let size;
199
+ try {
200
+ size = (await stat(file)).size;
201
+ }
202
+ catch {
203
+ throw notFound('File');
204
+ }
205
+ return {
206
+ target,
207
+ path: file,
208
+ sha256: entry.sha256,
209
+ size,
210
+ open: () => createReadStream(file),
211
+ };
212
+ },
213
+ async npmPackage(product) {
214
+ const source = npmSources[product];
215
+ if (source === undefined)
216
+ return null;
217
+ const stored = await load();
218
+ if (stored && currentVersion(stored, product) !== null)
219
+ return null;
220
+ return {
221
+ kind: 'npm',
222
+ product,
223
+ version: source.version,
224
+ package: source.package,
225
+ channel,
226
+ };
227
+ },
228
+ };
229
+ }
230
+ /** `agents.dist.npm`, checked when the service is created so a wrong entry fails the start rather than an install. */
231
+ function parseNpmSources(npm) {
232
+ const parsed = DistNpmConfigSchema.safeParse(npm ?? {});
233
+ if (!parsed.success)
234
+ throw new Error(`agents.dist.npm names each product's npm package and exact version, such as { nocobase-runner: { package: '@nocobase/agent-runner', version: '1.0.0' } }: ${parsed.error.issues
235
+ .map((issue) => `${issue.path.join('.')}: ${issue.message}`)
236
+ .join('; ')}`);
237
+ return parsed.data;
238
+ }
239
+ //# sourceMappingURL=artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ export interface DownloadToken {
2
+ readonly token: string;
3
+ readonly expiresAt: string;
4
+ readonly maxDownloads: number;
5
+ }
6
+ export interface Clock {
7
+ now(): Date;
8
+ }
9
+ /** How long a download token stays usable. */
10
+ export declare const DOWNLOAD_TOKEN_TTL_MS: number;
11
+ /** How many tarball downloads one token allows, so a failed download can be retried. */
12
+ export declare const DOWNLOAD_TOKEN_MAX_DOWNLOADS = 3;
13
+ export interface DownloadTokenRecord {
14
+ readonly id: string;
15
+ readonly tokenHash: string;
16
+ readonly createdById: string | null;
17
+ readonly target: string | null;
18
+ readonly downloads: number;
19
+ readonly expiresAt: string;
20
+ readonly createdAt: string;
21
+ }
22
+ /** Storage operations within the host's transaction. Updates must compare the stored download count atomically. */
23
+ export interface DownloadTokenTransaction {
24
+ insert(record: DownloadTokenRecord): Promise<void>;
25
+ findByHash(hash: string): Promise<DownloadTokenRecord | null | undefined>;
26
+ updateIfDownloads(id: string, expected: number, values: {
27
+ target: string | null;
28
+ downloads: number;
29
+ }): Promise<boolean>;
30
+ }
31
+ export interface DownloadTokenStore {
32
+ transaction<T>(fn: (store: DownloadTokenTransaction) => Promise<T>): Promise<T>;
33
+ }
34
+ /** What a request with a download token asks for. */
35
+ export interface DownloadRequest {
36
+ readonly product?: string;
37
+ /** The platform it resolves or downloads for; absent for the manifest, and for the universal tarball. */
38
+ readonly target?: string;
39
+ /** A tarball download, which counts against the token. */
40
+ readonly download?: boolean;
41
+ }
42
+ export interface DownloadTokenService {
43
+ create(createdById: string | null): Promise<DownloadToken>;
44
+ /** Lets `request` through with `token`, binding its platform and counting a download; `DOWNLOAD_TOKEN_INVALID` otherwise. */
45
+ admit(token: string, request: DownloadRequest): Promise<void>;
46
+ }
47
+ export interface DownloadTokenServiceDeps {
48
+ /** The one product a download token downloads: the application's CLI (`agents.cli.name`). */
49
+ readonly product: string;
50
+ readonly store: DownloadTokenStore;
51
+ readonly ids: {
52
+ next(): string;
53
+ };
54
+ readonly clock: Clock;
55
+ }
56
+ export declare function createDownloadTokenService(deps: DownloadTokenServiceDeps): DownloadTokenService;
57
+ //# sourceMappingURL=download-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download-tokens.d.ts","sourceRoot":"","sources":["../src/download-tokens.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AACD,MAAM,WAAW,KAAK;IACpB,GAAG,IAAI,IAAI,CAAC;CACb;AACD,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,EAAE,MAAoB,CAAC;AAEzD,wFAAwF;AACxF,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,mHAAmH;AACnH,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAC1E,iBAAiB,CACf,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GACnD,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB;AACD,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,CAAC,EACX,EAAE,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAClD,OAAO,CAAC,CAAC,CAAC,CAAC;CACf;AAED,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yGAAyG;IACzG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC3D,6HAA6H;IAC7H,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAED,MAAM,WAAW,wBAAwB;IACvC,6FAA6F;IAC7F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE;QAAE,IAAI,IAAI,MAAM,CAAA;KAAE,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,wBAAwB,GAC7B,oBAAoB,CAsEtB"}