@lp-labs/asset-license-tracker 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Libin Prasanth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,69 @@
1
- # Temporary Holding Version
1
+ # @lp-labs/asset-license-tracker
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
+ Create deterministic, machine-readable license metadata reports for assets. All identifiers,
4
+ license labels, restrictions, attribution, and notice text come from the caller; this package ships
5
+ no license corpus, does not reproduce license texts, and does not give legal advice or determine
6
+ license obligations.
7
+
8
+ ## Install and quick start
9
+
10
+ ```sh
11
+ npm install @lp-labs/asset-license-tracker
12
+ ```
13
+
14
+ ```js
15
+ import { createAssetLicenseReport, renderExportNotices } from '@lp-labs/asset-license-tracker';
16
+
17
+ const report = createAssetLicenseReport([
18
+ {
19
+ id: 'hero-photo',
20
+ name: 'Campaign hero photo',
21
+ license: {
22
+ id: 'provider-record-42',
23
+ label: 'Provider-supplied license label',
24
+ attributionRequired: true,
25
+ attribution: 'Photo by Example Artist',
26
+ notice: 'Used under the supplied campaign agreement',
27
+ },
28
+ restrictions: ['Do not redistribute the source file'],
29
+ },
30
+ ]);
31
+ console.log(report.restrictions);
32
+ console.log(renderExportNotices(report));
33
+ ```
34
+
35
+ ## API and types
36
+
37
+ - `createAssetLicenseReport(assets, limits?)` validates caller-supplied metadata and returns
38
+ `AssetLicenseReport` with entries, attributions, restrictions, and export notices.
39
+ - `renderExportNotices(report)` joins only supplied attribution and notice strings, in input order.
40
+ - `LicensedAsset` contains `id`, `name`, `license`, and a list of restriction strings.
41
+ - `AssetLicenseMetadata` contains a caller-defined license `id` and `label`,
42
+ `attributionRequired`, and optional `attribution` and `notice` text.
43
+ - `LicenseReportLimits` bounds asset count, text length, and restrictions per asset.
44
+ - `LicenseMetadataError` reports invalid records and missing required attribution.
45
+ - Types: `AssetLicenseReport`, `LicenseReportEntry`, and the input/limit types above.
46
+
47
+ Default limits are 10,000 assets, 4,096 characters per text value, and 100 restrictions per asset.
48
+ Limits must be positive safe integers. Identifiers must be unique within one report. A
49
+ `attributionRequired` flag is merely reported from caller data; the package does not determine
50
+ whether that assertion is correct, interpret restrictions, fetch terms, or verify permissions.
51
+ Keep source metadata and any legal review in your own system of record.
52
+ Report construction and notice rendering are linear in the asset, restriction, and supplied text
53
+ size, bounded by the configured capacities.
54
+
55
+ ## Module formats and customization
56
+
57
+ ESM, CommonJS, and TypeScript declarations are included. CommonJS:
58
+ `const { createAssetLicenseReport } = require('@lp-labs/asset-license-tracker')`. Extend the
59
+ caller-owned metadata model in your application and map it into `LicensedAsset`; this API rejects
60
+ unknown fields rather than guessing their meaning. No full license text is bundled. There are no
61
+ runtime or peer dependencies; TypeScript and Prettier are development-only.
62
+
63
+ ## Support
64
+
65
+ If this library is useful to you, consider supporting its maintenance with a [donation via PayPal](https://www.paypal.me/LibinPrasanth).
66
+
67
+ ## Author
68
+
69
+ Created by [Libin Prasanth](https://www.linkedin.com/in/libinprasanth/). Connect on LinkedIn for updates and discussion.
@@ -0,0 +1,49 @@
1
+ export interface AssetLicenseMetadata {
2
+ readonly id: string;
3
+ readonly label: string;
4
+ readonly attributionRequired: boolean;
5
+ readonly attribution?: string;
6
+ readonly notice?: string;
7
+ }
8
+ export interface LicensedAsset {
9
+ readonly id: string;
10
+ readonly name: string;
11
+ readonly license: AssetLicenseMetadata;
12
+ readonly restrictions: readonly string[];
13
+ }
14
+ export interface LicenseReportLimits {
15
+ readonly maxAssets?: number;
16
+ readonly maxTextLength?: number;
17
+ readonly maxRestrictionsPerAsset?: number;
18
+ }
19
+ export interface LicenseReportEntry {
20
+ readonly assetId: string;
21
+ readonly assetName: string;
22
+ readonly licenseId: string;
23
+ readonly licenseLabel: string;
24
+ readonly attributionRequired: boolean;
25
+ readonly attribution?: string;
26
+ readonly restrictions: readonly string[];
27
+ readonly notice?: string;
28
+ }
29
+ export interface AssetLicenseReport {
30
+ readonly assetCount: number;
31
+ readonly entries: readonly LicenseReportEntry[];
32
+ readonly attributions: readonly {
33
+ readonly assetId: string;
34
+ readonly text: string;
35
+ }[];
36
+ readonly restrictions: readonly {
37
+ readonly assetId: string;
38
+ readonly text: string;
39
+ }[];
40
+ readonly notices: readonly {
41
+ readonly assetId: string;
42
+ readonly text: string;
43
+ }[];
44
+ }
45
+ export declare class LicenseMetadataError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ export declare function createAssetLicenseReport(assets: readonly LicensedAsset[], inputLimits?: LicenseReportLimits): AssetLicenseReport;
49
+ export declare function renderExportNotices(report: AssetLicenseReport): string;
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LicenseMetadataError = void 0;
4
+ exports.createAssetLicenseReport = createAssetLicenseReport;
5
+ exports.renderExportNotices = renderExportNotices;
6
+ class LicenseMetadataError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'LicenseMetadataError';
10
+ }
11
+ }
12
+ exports.LicenseMetadataError = LicenseMetadataError;
13
+ const defaults = {
14
+ maxAssets: 10_000,
15
+ maxTextLength: 4_096,
16
+ maxRestrictionsPerAsset: 100,
17
+ };
18
+ const assetKeys = new Set(['id', 'name', 'license', 'restrictions']);
19
+ const licenseKeys = new Set(['id', 'label', 'attributionRequired', 'attribution', 'notice']);
20
+ function assertRecord(value, label, allowed, required) {
21
+ if (value === null ||
22
+ typeof value !== 'object' ||
23
+ Array.isArray(value) ||
24
+ (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
25
+ throw new LicenseMetadataError(`${label} must be a plain record.`);
26
+ }
27
+ for (const key of Reflect.ownKeys(value)) {
28
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
29
+ if (typeof key !== 'string' ||
30
+ !allowed.has(key) ||
31
+ !descriptor.enumerable ||
32
+ !('value' in descriptor)) {
33
+ throw new LicenseMetadataError(`${label} has an unknown or non-data field.`);
34
+ }
35
+ }
36
+ if (required.some((key) => !Object.hasOwn(value, key))) {
37
+ throw new LicenseMetadataError(`${label} is missing a required field.`);
38
+ }
39
+ }
40
+ function limitsOf(input) {
41
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
42
+ throw new RangeError('License report limits must be a plain record.');
43
+ }
44
+ for (const key of Reflect.ownKeys(input)) {
45
+ const descriptor = typeof key === 'string' ? Object.getOwnPropertyDescriptor(input, key) : undefined;
46
+ if (!descriptor ||
47
+ !['maxAssets', 'maxTextLength', 'maxRestrictionsPerAsset'].includes(key) ||
48
+ !descriptor.enumerable ||
49
+ !('value' in descriptor)) {
50
+ throw new RangeError('License report limits contain unknown fields.');
51
+ }
52
+ }
53
+ const limits = {
54
+ maxAssets: input.maxAssets ?? defaults.maxAssets,
55
+ maxTextLength: input.maxTextLength ?? defaults.maxTextLength,
56
+ maxRestrictionsPerAsset: input.maxRestrictionsPerAsset ?? defaults.maxRestrictionsPerAsset,
57
+ };
58
+ for (const [name, value] of Object.entries(limits)) {
59
+ if (!Number.isSafeInteger(value) || value < 1) {
60
+ throw new RangeError(`${name} must be a positive safe integer.`);
61
+ }
62
+ }
63
+ return limits;
64
+ }
65
+ function text(value, name, maximum) {
66
+ if (typeof value !== 'string' || value.trim().length === 0 || value.length > maximum) {
67
+ throw new LicenseMetadataError(`${name} must be a non-empty string up to ${maximum} characters.`);
68
+ }
69
+ return value;
70
+ }
71
+ function optionalText(value, name, maximum) {
72
+ return value === undefined ? undefined : text(value, name, maximum);
73
+ }
74
+ function validateAsset(value, limits) {
75
+ assertRecord(value, 'Asset', assetKeys, ['id', 'name', 'license', 'restrictions']);
76
+ const id = text(value.id, 'asset.id', limits.maxTextLength);
77
+ const assetName = text(value.name, 'asset.name', limits.maxTextLength);
78
+ assertRecord(value.license, 'License metadata', licenseKeys, [
79
+ 'id',
80
+ 'label',
81
+ 'attributionRequired',
82
+ ]);
83
+ const licenseId = text(value.license.id, 'license.id', limits.maxTextLength);
84
+ const licenseLabel = text(value.license.label, 'license.label', limits.maxTextLength);
85
+ if (typeof value.license.attributionRequired !== 'boolean') {
86
+ throw new LicenseMetadataError('license.attributionRequired must be a boolean.');
87
+ }
88
+ const attribution = optionalText(value.license.attribution, 'license.attribution', limits.maxTextLength);
89
+ const notice = optionalText(value.license.notice, 'license.notice', limits.maxTextLength);
90
+ if (value.license.attributionRequired && attribution === undefined) {
91
+ throw new LicenseMetadataError('Required attribution text was not supplied.');
92
+ }
93
+ if (!Array.isArray(value.restrictions) ||
94
+ value.restrictions.length > limits.maxRestrictionsPerAsset) {
95
+ throw new RangeError('Asset restrictions must be a bounded string array.');
96
+ }
97
+ const restrictions = [];
98
+ for (let index = 0; index < value.restrictions.length; index += 1) {
99
+ const descriptor = Object.getOwnPropertyDescriptor(value.restrictions, String(index));
100
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
101
+ throw new LicenseMetadataError('Restrictions must be dense enumerable data values.');
102
+ }
103
+ restrictions.push(text(descriptor.value, `restrictions[${index}]`, limits.maxTextLength));
104
+ }
105
+ for (const key of Reflect.ownKeys(value.restrictions)) {
106
+ if (key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key))) {
107
+ throw new LicenseMetadataError('Restrictions cannot have extra properties.');
108
+ }
109
+ }
110
+ if (new Set(restrictions).size !== restrictions.length) {
111
+ throw new LicenseMetadataError('Duplicate restriction entries are not allowed.');
112
+ }
113
+ return {
114
+ assetId: id,
115
+ assetName,
116
+ licenseId,
117
+ licenseLabel,
118
+ attributionRequired: value.license.attributionRequired,
119
+ ...(attribution === undefined ? {} : { attribution }),
120
+ restrictions,
121
+ ...(notice === undefined ? {} : { notice }),
122
+ };
123
+ }
124
+ function createAssetLicenseReport(assets, inputLimits = {}) {
125
+ if (!Array.isArray(assets))
126
+ throw new LicenseMetadataError('Assets must be an array.');
127
+ const limits = limitsOf(inputLimits);
128
+ if (assets.length > limits.maxAssets)
129
+ throw new RangeError('Asset count exceeds capacity.');
130
+ const entries = [];
131
+ for (let index = 0; index < assets.length; index += 1) {
132
+ const descriptor = Object.getOwnPropertyDescriptor(assets, String(index));
133
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
134
+ throw new LicenseMetadataError('Assets must be a dense array of enumerable data values.');
135
+ }
136
+ entries.push(validateAsset(descriptor.value, limits));
137
+ }
138
+ for (const key of Reflect.ownKeys(assets)) {
139
+ if (key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key))) {
140
+ throw new LicenseMetadataError('Asset arrays cannot have extra properties.');
141
+ }
142
+ }
143
+ const ids = new Set();
144
+ for (const entry of entries) {
145
+ if (ids.has(entry.assetId))
146
+ throw new LicenseMetadataError(`Duplicate asset id: ${entry.assetId}`);
147
+ ids.add(entry.assetId);
148
+ }
149
+ return {
150
+ assetCount: entries.length,
151
+ entries,
152
+ attributions: entries.flatMap((entry) => entry.attribution === undefined ? [] : [{ assetId: entry.assetId, text: entry.attribution }]),
153
+ restrictions: entries.flatMap((entry) => entry.restrictions.map((restriction) => ({
154
+ assetId: entry.assetId,
155
+ text: restriction,
156
+ }))),
157
+ notices: entries.flatMap((entry) => entry.notice === undefined ? [] : [{ assetId: entry.assetId, text: entry.notice }]),
158
+ };
159
+ }
160
+ function renderExportNotices(report) {
161
+ assertRecord(report, 'License report', new Set(['assetCount', 'entries', 'attributions', 'restrictions', 'notices']), ['assetCount', 'entries', 'attributions', 'restrictions', 'notices']);
162
+ if (!Array.isArray(report.entries) ||
163
+ !Array.isArray(report.attributions) ||
164
+ !Array.isArray(report.restrictions) ||
165
+ !Array.isArray(report.notices) ||
166
+ !Number.isSafeInteger(report.assetCount) ||
167
+ report.entries.length !== report.assetCount) {
168
+ throw new LicenseMetadataError('A valid report is required.');
169
+ }
170
+ const lines = [];
171
+ for (let index = 0; index < report.entries.length; index += 1) {
172
+ const descriptor = Object.getOwnPropertyDescriptor(report.entries, String(index));
173
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
174
+ throw new LicenseMetadataError('Report entries must be dense enumerable data values.');
175
+ }
176
+ const entry = descriptor.value;
177
+ assertRecord(entry, 'License report entry', new Set([
178
+ 'assetId',
179
+ 'assetName',
180
+ 'licenseId',
181
+ 'licenseLabel',
182
+ 'attributionRequired',
183
+ 'attribution',
184
+ 'restrictions',
185
+ 'notice',
186
+ ]), ['assetId', 'assetName', 'licenseId', 'licenseLabel', 'attributionRequired', 'restrictions']);
187
+ if (typeof entry.attributionRequired !== 'boolean' ||
188
+ typeof entry.assetId !== 'string' ||
189
+ typeof entry.assetName !== 'string' ||
190
+ typeof entry.licenseId !== 'string' ||
191
+ typeof entry.licenseLabel !== 'string' ||
192
+ !Array.isArray(entry.restrictions)) {
193
+ throw new LicenseMetadataError('License report entry is malformed.');
194
+ }
195
+ const attribution = entry.attribution;
196
+ const notice = entry.notice;
197
+ if (entry.attributionRequired && attribution === undefined) {
198
+ throw new LicenseMetadataError('Required attribution text is missing from this report.');
199
+ }
200
+ if (attribution !== undefined)
201
+ lines.push(text(attribution, 'attribution', Number.MAX_SAFE_INTEGER));
202
+ if (notice !== undefined)
203
+ lines.push(text(notice, 'notice', Number.MAX_SAFE_INTEGER));
204
+ }
205
+ return lines.join('\n');
206
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,49 @@
1
+ export interface AssetLicenseMetadata {
2
+ readonly id: string;
3
+ readonly label: string;
4
+ readonly attributionRequired: boolean;
5
+ readonly attribution?: string;
6
+ readonly notice?: string;
7
+ }
8
+ export interface LicensedAsset {
9
+ readonly id: string;
10
+ readonly name: string;
11
+ readonly license: AssetLicenseMetadata;
12
+ readonly restrictions: readonly string[];
13
+ }
14
+ export interface LicenseReportLimits {
15
+ readonly maxAssets?: number;
16
+ readonly maxTextLength?: number;
17
+ readonly maxRestrictionsPerAsset?: number;
18
+ }
19
+ export interface LicenseReportEntry {
20
+ readonly assetId: string;
21
+ readonly assetName: string;
22
+ readonly licenseId: string;
23
+ readonly licenseLabel: string;
24
+ readonly attributionRequired: boolean;
25
+ readonly attribution?: string;
26
+ readonly restrictions: readonly string[];
27
+ readonly notice?: string;
28
+ }
29
+ export interface AssetLicenseReport {
30
+ readonly assetCount: number;
31
+ readonly entries: readonly LicenseReportEntry[];
32
+ readonly attributions: readonly {
33
+ readonly assetId: string;
34
+ readonly text: string;
35
+ }[];
36
+ readonly restrictions: readonly {
37
+ readonly assetId: string;
38
+ readonly text: string;
39
+ }[];
40
+ readonly notices: readonly {
41
+ readonly assetId: string;
42
+ readonly text: string;
43
+ }[];
44
+ }
45
+ export declare class LicenseMetadataError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ export declare function createAssetLicenseReport(assets: readonly LicensedAsset[], inputLimits?: LicenseReportLimits): AssetLicenseReport;
49
+ export declare function renderExportNotices(report: AssetLicenseReport): string;
@@ -0,0 +1,200 @@
1
+ export class LicenseMetadataError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'LicenseMetadataError';
5
+ }
6
+ }
7
+ const defaults = {
8
+ maxAssets: 10_000,
9
+ maxTextLength: 4_096,
10
+ maxRestrictionsPerAsset: 100,
11
+ };
12
+ const assetKeys = new Set(['id', 'name', 'license', 'restrictions']);
13
+ const licenseKeys = new Set(['id', 'label', 'attributionRequired', 'attribution', 'notice']);
14
+ function assertRecord(value, label, allowed, required) {
15
+ if (value === null ||
16
+ typeof value !== 'object' ||
17
+ Array.isArray(value) ||
18
+ (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
19
+ throw new LicenseMetadataError(`${label} must be a plain record.`);
20
+ }
21
+ for (const key of Reflect.ownKeys(value)) {
22
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
23
+ if (typeof key !== 'string' ||
24
+ !allowed.has(key) ||
25
+ !descriptor.enumerable ||
26
+ !('value' in descriptor)) {
27
+ throw new LicenseMetadataError(`${label} has an unknown or non-data field.`);
28
+ }
29
+ }
30
+ if (required.some((key) => !Object.hasOwn(value, key))) {
31
+ throw new LicenseMetadataError(`${label} is missing a required field.`);
32
+ }
33
+ }
34
+ function limitsOf(input) {
35
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
36
+ throw new RangeError('License report limits must be a plain record.');
37
+ }
38
+ for (const key of Reflect.ownKeys(input)) {
39
+ const descriptor = typeof key === 'string' ? Object.getOwnPropertyDescriptor(input, key) : undefined;
40
+ if (!descriptor ||
41
+ !['maxAssets', 'maxTextLength', 'maxRestrictionsPerAsset'].includes(key) ||
42
+ !descriptor.enumerable ||
43
+ !('value' in descriptor)) {
44
+ throw new RangeError('License report limits contain unknown fields.');
45
+ }
46
+ }
47
+ const limits = {
48
+ maxAssets: input.maxAssets ?? defaults.maxAssets,
49
+ maxTextLength: input.maxTextLength ?? defaults.maxTextLength,
50
+ maxRestrictionsPerAsset: input.maxRestrictionsPerAsset ?? defaults.maxRestrictionsPerAsset,
51
+ };
52
+ for (const [name, value] of Object.entries(limits)) {
53
+ if (!Number.isSafeInteger(value) || value < 1) {
54
+ throw new RangeError(`${name} must be a positive safe integer.`);
55
+ }
56
+ }
57
+ return limits;
58
+ }
59
+ function text(value, name, maximum) {
60
+ if (typeof value !== 'string' || value.trim().length === 0 || value.length > maximum) {
61
+ throw new LicenseMetadataError(`${name} must be a non-empty string up to ${maximum} characters.`);
62
+ }
63
+ return value;
64
+ }
65
+ function optionalText(value, name, maximum) {
66
+ return value === undefined ? undefined : text(value, name, maximum);
67
+ }
68
+ function validateAsset(value, limits) {
69
+ assertRecord(value, 'Asset', assetKeys, ['id', 'name', 'license', 'restrictions']);
70
+ const id = text(value.id, 'asset.id', limits.maxTextLength);
71
+ const assetName = text(value.name, 'asset.name', limits.maxTextLength);
72
+ assertRecord(value.license, 'License metadata', licenseKeys, [
73
+ 'id',
74
+ 'label',
75
+ 'attributionRequired',
76
+ ]);
77
+ const licenseId = text(value.license.id, 'license.id', limits.maxTextLength);
78
+ const licenseLabel = text(value.license.label, 'license.label', limits.maxTextLength);
79
+ if (typeof value.license.attributionRequired !== 'boolean') {
80
+ throw new LicenseMetadataError('license.attributionRequired must be a boolean.');
81
+ }
82
+ const attribution = optionalText(value.license.attribution, 'license.attribution', limits.maxTextLength);
83
+ const notice = optionalText(value.license.notice, 'license.notice', limits.maxTextLength);
84
+ if (value.license.attributionRequired && attribution === undefined) {
85
+ throw new LicenseMetadataError('Required attribution text was not supplied.');
86
+ }
87
+ if (!Array.isArray(value.restrictions) ||
88
+ value.restrictions.length > limits.maxRestrictionsPerAsset) {
89
+ throw new RangeError('Asset restrictions must be a bounded string array.');
90
+ }
91
+ const restrictions = [];
92
+ for (let index = 0; index < value.restrictions.length; index += 1) {
93
+ const descriptor = Object.getOwnPropertyDescriptor(value.restrictions, String(index));
94
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
95
+ throw new LicenseMetadataError('Restrictions must be dense enumerable data values.');
96
+ }
97
+ restrictions.push(text(descriptor.value, `restrictions[${index}]`, limits.maxTextLength));
98
+ }
99
+ for (const key of Reflect.ownKeys(value.restrictions)) {
100
+ if (key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key))) {
101
+ throw new LicenseMetadataError('Restrictions cannot have extra properties.');
102
+ }
103
+ }
104
+ if (new Set(restrictions).size !== restrictions.length) {
105
+ throw new LicenseMetadataError('Duplicate restriction entries are not allowed.');
106
+ }
107
+ return {
108
+ assetId: id,
109
+ assetName,
110
+ licenseId,
111
+ licenseLabel,
112
+ attributionRequired: value.license.attributionRequired,
113
+ ...(attribution === undefined ? {} : { attribution }),
114
+ restrictions,
115
+ ...(notice === undefined ? {} : { notice }),
116
+ };
117
+ }
118
+ export function createAssetLicenseReport(assets, inputLimits = {}) {
119
+ if (!Array.isArray(assets))
120
+ throw new LicenseMetadataError('Assets must be an array.');
121
+ const limits = limitsOf(inputLimits);
122
+ if (assets.length > limits.maxAssets)
123
+ throw new RangeError('Asset count exceeds capacity.');
124
+ const entries = [];
125
+ for (let index = 0; index < assets.length; index += 1) {
126
+ const descriptor = Object.getOwnPropertyDescriptor(assets, String(index));
127
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
128
+ throw new LicenseMetadataError('Assets must be a dense array of enumerable data values.');
129
+ }
130
+ entries.push(validateAsset(descriptor.value, limits));
131
+ }
132
+ for (const key of Reflect.ownKeys(assets)) {
133
+ if (key !== 'length' && (typeof key !== 'string' || !/^(0|[1-9]\d*)$/.test(key))) {
134
+ throw new LicenseMetadataError('Asset arrays cannot have extra properties.');
135
+ }
136
+ }
137
+ const ids = new Set();
138
+ for (const entry of entries) {
139
+ if (ids.has(entry.assetId))
140
+ throw new LicenseMetadataError(`Duplicate asset id: ${entry.assetId}`);
141
+ ids.add(entry.assetId);
142
+ }
143
+ return {
144
+ assetCount: entries.length,
145
+ entries,
146
+ attributions: entries.flatMap((entry) => entry.attribution === undefined ? [] : [{ assetId: entry.assetId, text: entry.attribution }]),
147
+ restrictions: entries.flatMap((entry) => entry.restrictions.map((restriction) => ({
148
+ assetId: entry.assetId,
149
+ text: restriction,
150
+ }))),
151
+ notices: entries.flatMap((entry) => entry.notice === undefined ? [] : [{ assetId: entry.assetId, text: entry.notice }]),
152
+ };
153
+ }
154
+ export function renderExportNotices(report) {
155
+ assertRecord(report, 'License report', new Set(['assetCount', 'entries', 'attributions', 'restrictions', 'notices']), ['assetCount', 'entries', 'attributions', 'restrictions', 'notices']);
156
+ if (!Array.isArray(report.entries) ||
157
+ !Array.isArray(report.attributions) ||
158
+ !Array.isArray(report.restrictions) ||
159
+ !Array.isArray(report.notices) ||
160
+ !Number.isSafeInteger(report.assetCount) ||
161
+ report.entries.length !== report.assetCount) {
162
+ throw new LicenseMetadataError('A valid report is required.');
163
+ }
164
+ const lines = [];
165
+ for (let index = 0; index < report.entries.length; index += 1) {
166
+ const descriptor = Object.getOwnPropertyDescriptor(report.entries, String(index));
167
+ if (!descriptor || !descriptor.enumerable || !('value' in descriptor)) {
168
+ throw new LicenseMetadataError('Report entries must be dense enumerable data values.');
169
+ }
170
+ const entry = descriptor.value;
171
+ assertRecord(entry, 'License report entry', new Set([
172
+ 'assetId',
173
+ 'assetName',
174
+ 'licenseId',
175
+ 'licenseLabel',
176
+ 'attributionRequired',
177
+ 'attribution',
178
+ 'restrictions',
179
+ 'notice',
180
+ ]), ['assetId', 'assetName', 'licenseId', 'licenseLabel', 'attributionRequired', 'restrictions']);
181
+ if (typeof entry.attributionRequired !== 'boolean' ||
182
+ typeof entry.assetId !== 'string' ||
183
+ typeof entry.assetName !== 'string' ||
184
+ typeof entry.licenseId !== 'string' ||
185
+ typeof entry.licenseLabel !== 'string' ||
186
+ !Array.isArray(entry.restrictions)) {
187
+ throw new LicenseMetadataError('License report entry is malformed.');
188
+ }
189
+ const attribution = entry.attribution;
190
+ const notice = entry.notice;
191
+ if (entry.attributionRequired && attribution === undefined) {
192
+ throw new LicenseMetadataError('Required attribution text is missing from this report.');
193
+ }
194
+ if (attribution !== undefined)
195
+ lines.push(text(attribution, 'attribution', Number.MAX_SAFE_INTEGER));
196
+ if (notice !== undefined)
197
+ lines.push(text(notice, 'notice', Number.MAX_SAFE_INTEGER));
198
+ }
199
+ return lines.join('\n');
200
+ }
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@lp-labs/asset-license-tracker",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Caller-supplied asset license metadata and deterministic export notices.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/cjs/index.js",
8
+ "module": "./dist/esm/index.js",
9
+ "types": "./dist/esm/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/esm/index.d.ts",
14
+ "default": "./dist/esm/index.js"
15
+ },
16
+ "require": {
17
+ "types": "./dist/cjs/index.d.ts",
18
+ "default": "./dist/cjs/index.js"
19
+ }
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "sideEffects": false,
29
+ "engines": {
30
+ "node": "\u003e=18"
31
+ },
32
+ "scripts": {
33
+ "build": "node scripts/build.mjs",
34
+ "typecheck": "npm run build \u0026\u0026 node scripts/check-types.mjs",
35
+ "test": "npm run build \u0026\u0026 node --test tests/*.test.mjs",
36
+ "format": "prettier --write src scripts tests *.json README.md",
37
+ "format:check": "prettier --check src scripts tests *.json README.md",
38
+ "prepack": "npm run build"
39
+ },
40
+ "devDependencies": {
41
+ "typescript": "^5.9.3",
42
+ "prettier": "^3.6.2"
43
+ },
44
+ "prettier": {
45
+ "singleQuote": true,
46
+ "semi": true,
47
+ "trailingComma": "all",
48
+ "printWidth": 100,
49
+ "endOfLine": "lf"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public",
53
+ "registry": "https://registry.npmjs.org/"
54
+ },
55
+ "author": {
56
+ "name": "Libin Prasanth",
57
+ "url": "https://www.linkedin.com/in/libinprasanth/"
58
+ },
59
+ "funding": {
60
+ "type": "paypal",
61
+ "url": "https://www.paypal.me/LibinPrasanth"
62
+ }
63
+ }