@braidlabs/build 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -0
- package/src/bin/skew-contract.d.ts +2 -0
- package/src/bin/skew-contract.js +73 -0
- package/src/bin/skew-contract.js.map +1 -0
- package/src/bin/skew-stamp.d.ts +2 -0
- package/src/bin/skew-stamp.js +55 -0
- package/src/bin/skew-stamp.js.map +1 -0
- package/src/index.d.ts +2 -0
- package/src/index.js +3 -0
- package/src/index.js.map +1 -0
- package/src/lib/contract-gen.d.ts +21 -0
- package/src/lib/contract-gen.js +126 -0
- package/src/lib/contract-gen.js.map +1 -0
- package/src/lib/stamp.d.ts +58 -0
- package/src/lib/stamp.js +97 -0
- package/src/lib/stamp.js.map +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@braidlabs/build",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Stamps build identity and emits the skew manifest so clients and servers can discover that they disagree.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"skew-stamp": "./src/bin/skew-stamp.js",
|
|
11
|
+
"skew-contract": "./src/bin/skew-contract.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./src/index.d.ts",
|
|
16
|
+
"default": "./src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/braidjs/braid.git",
|
|
27
|
+
"directory": "libs/build"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/braidjs/braid#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/braidjs/braid/issues"
|
|
32
|
+
},
|
|
33
|
+
"module": "./src/index.js"
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generateContractFile } from '../lib/contract-gen.js';
|
|
3
|
+
/**
|
|
4
|
+
* skew-contract — tooling for contract documents.
|
|
5
|
+
*
|
|
6
|
+
* skew-contract gen --in contracts/portfolio-fund.json \
|
|
7
|
+
* --out src/generated/portfolio-fund.contract.ts
|
|
8
|
+
*/
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const flags = {};
|
|
11
|
+
const positional = [];
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const token = argv[i];
|
|
14
|
+
if (token === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
if (!token.startsWith('--')) {
|
|
17
|
+
positional.push(token);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const key = token.slice(2);
|
|
21
|
+
const next = argv[i + 1];
|
|
22
|
+
if (next === undefined || next.startsWith('--')) {
|
|
23
|
+
flags[key] = true;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
flags[key] = next;
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { positional, flags };
|
|
31
|
+
}
|
|
32
|
+
const USAGE = `skew-contract — generate frozen types from a contract document
|
|
33
|
+
|
|
34
|
+
skew-contract gen --in <contract.json> --out <generated.ts> [--type-prefix <Name>] [--const-name <name>]
|
|
35
|
+
|
|
36
|
+
gen Generate a TypeScript module: one frozen interface per
|
|
37
|
+
documented version, plus the document as a typed const.
|
|
38
|
+
--in <path> Contract document JSON (required)
|
|
39
|
+
--out <path> TypeScript file to write (required)
|
|
40
|
+
--type-prefix <str> Override the PascalCase prefix derived from the name
|
|
41
|
+
--const-name <str> Override the exported document const's name
|
|
42
|
+
--help
|
|
43
|
+
`;
|
|
44
|
+
function main() {
|
|
45
|
+
var _a;
|
|
46
|
+
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
47
|
+
if (flags['help'] || positional[0] === 'help') {
|
|
48
|
+
process.stdout.write(USAGE);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const command = (_a = positional[0]) !== null && _a !== void 0 ? _a : 'gen';
|
|
52
|
+
if (command !== 'gen') {
|
|
53
|
+
process.stderr.write(`skew-contract: unknown command "${command}"\n\n${USAGE}`);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const input = flags['in'];
|
|
58
|
+
const output = flags['out'];
|
|
59
|
+
if (typeof input !== 'string' || typeof output !== 'string') {
|
|
60
|
+
process.stderr.write(`skew-contract gen: --in and --out are both required\n\n${USAGE}`);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const result = generateContractFile({
|
|
65
|
+
in: input,
|
|
66
|
+
out: output,
|
|
67
|
+
typePrefix: typeof flags['type-prefix'] === 'string' ? flags['type-prefix'] : undefined,
|
|
68
|
+
constName: typeof flags['const-name'] === 'string' ? flags['const-name'] : undefined,
|
|
69
|
+
});
|
|
70
|
+
process.stdout.write(`skew-contract: "${result.name}" → ${result.out}\n`);
|
|
71
|
+
}
|
|
72
|
+
main();
|
|
73
|
+
//# sourceMappingURL=skew-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skew-contract.js","sourceRoot":"","sources":["../../../../../libs/build/src/bin/skew-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stamp } from '../lib/stamp.js';
|
|
3
|
+
/**
|
|
4
|
+
* skew-stamp — writes build identity and the skew manifest.
|
|
5
|
+
*
|
|
6
|
+
* skew-stamp --out src/generated/build-id.ts \
|
|
7
|
+
* --manifest dist/app/browser/skew-manifest.json \
|
|
8
|
+
* --assets dist/app/browser
|
|
9
|
+
*/
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = {};
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const token = argv[i];
|
|
14
|
+
if (!(token === null || token === void 0 ? void 0 : token.startsWith('--')))
|
|
15
|
+
continue;
|
|
16
|
+
const key = token.slice(2);
|
|
17
|
+
const next = argv[i + 1];
|
|
18
|
+
if (next === undefined || next.startsWith('--')) {
|
|
19
|
+
args[key] = true;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
args[key] = next;
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return args;
|
|
27
|
+
}
|
|
28
|
+
const USAGE = `skew-stamp — stamp build identity for version-skew detection
|
|
29
|
+
|
|
30
|
+
--out <path> TypeScript file to generate (default: src/generated/build-id.ts)
|
|
31
|
+
--manifest <path> JSON manifest to emit (optional, but required for probing)
|
|
32
|
+
--assets <dir> Output dir to scan for chunks(optional, enriches the manifest)
|
|
33
|
+
--build-id <id> Override the derived id (default: git SHA, else SKEW_BUILD_ID)
|
|
34
|
+
--built-at <iso> Override the timestamp
|
|
35
|
+
--help
|
|
36
|
+
`;
|
|
37
|
+
function main() {
|
|
38
|
+
const args = parseArgs(process.argv.slice(2));
|
|
39
|
+
if (args['help']) {
|
|
40
|
+
process.stdout.write(USAGE);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const out = typeof args['out'] === 'string' ? args['out'] : 'src/generated/build-id.ts';
|
|
44
|
+
const manifest = typeof args['manifest'] === 'string' ? args['manifest'] : undefined;
|
|
45
|
+
const assetDir = typeof args['assets'] === 'string' ? args['assets'] : undefined;
|
|
46
|
+
const buildId = typeof args['build-id'] === 'string' ? args['build-id'] : undefined;
|
|
47
|
+
const builtAt = typeof args['built-at'] === 'string' ? args['built-at'] : undefined;
|
|
48
|
+
const result = stamp({ out, manifest, assetDir, buildId, builtAt });
|
|
49
|
+
const moduleCount = Object.keys(result.modules).length;
|
|
50
|
+
process.stdout.write(`skew: ${result.buildId} @ ${result.builtAt}\n` +
|
|
51
|
+
` identity → ${out}\n` +
|
|
52
|
+
(manifest ? ` manifest → ${manifest}${moduleCount ? ` (${moduleCount} modules)` : ''}\n` : ''));
|
|
53
|
+
}
|
|
54
|
+
main();
|
|
55
|
+
//# sourceMappingURL=skew-stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skew-stamp.js","sourceRoot":"","sources":["../../../../../libs/build/src/bin/skew-stamp.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAExC;;;;;;GAMG;AACH,SAAS,SAAS,CAAC,IAAuB;IACxC,MAAM,IAAI,GAAqC,EAAE,CAAC;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,UAAU,CAAC,IAAI,CAAC,CAAA;YAAE,SAAS;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACjB,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,KAAK,GAAG;;;;;;;;CAQb,CAAC;AAEF,SAAS,IAAI;IACX,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,2BAA2B,CAAC;IACxF,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACrF,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACjF,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACpF,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEpF,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAEpE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,SAAS,MAAM,CAAC,OAAO,MAAM,MAAM,CAAC,OAAO,IAAI;QAC7C,gBAAgB,GAAG,IAAI;QACvB,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,WAAW,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAClG,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/build/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ContractGenOptions {
|
|
2
|
+
/** Overrides the type-name prefix derived from the contract name. */
|
|
3
|
+
readonly typePrefix?: string;
|
|
4
|
+
/** Name for the exported document const. Defaults to `<camelName>Contract`. */
|
|
5
|
+
readonly constName?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function pascalCase(name: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Renders the generated module: one frozen interface per documented version,
|
|
10
|
+
* a `Current` alias, and the document itself as a typed const.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateContractModule(rawDoc: unknown, options?: ContractGenOptions): string;
|
|
13
|
+
export interface ContractGenFileOptions extends ContractGenOptions {
|
|
14
|
+
readonly in: string;
|
|
15
|
+
readonly out: string;
|
|
16
|
+
}
|
|
17
|
+
/** Reads a contract JSON file and writes the generated module beside your source. */
|
|
18
|
+
export declare function generateContractFile(options: ContractGenFileOptions): {
|
|
19
|
+
name: string;
|
|
20
|
+
out: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
/** Validates just enough shape to generate from; full validation is runtime's job. */
|
|
4
|
+
function assertDocLike(raw) {
|
|
5
|
+
const fail = (message) => {
|
|
6
|
+
throw new TypeError(`skew-contract gen: ${message}`);
|
|
7
|
+
};
|
|
8
|
+
if (typeof raw !== 'object' || raw === null)
|
|
9
|
+
fail('document must be an object');
|
|
10
|
+
const doc = raw;
|
|
11
|
+
if (doc['skewContract'] !== '1')
|
|
12
|
+
fail('document must declare skewContract: "1"');
|
|
13
|
+
if (typeof doc['name'] !== 'string' || doc['name'].length === 0)
|
|
14
|
+
fail('document needs a name');
|
|
15
|
+
if (!Number.isInteger(doc['current']))
|
|
16
|
+
fail('document needs an integer current version');
|
|
17
|
+
if (!Array.isArray(doc['steps']))
|
|
18
|
+
fail('document needs a steps array');
|
|
19
|
+
return raw;
|
|
20
|
+
}
|
|
21
|
+
export function pascalCase(name) {
|
|
22
|
+
return name
|
|
23
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
24
|
+
.filter((part) => part.length > 0)
|
|
25
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
26
|
+
.join('');
|
|
27
|
+
}
|
|
28
|
+
function camelCase(name) {
|
|
29
|
+
const pascal = pascalCase(name);
|
|
30
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
31
|
+
}
|
|
32
|
+
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
33
|
+
/** Renders a (subset of) JSON Schema as a TypeScript type expression. */
|
|
34
|
+
function tsType(schema, indent) {
|
|
35
|
+
var _a;
|
|
36
|
+
if (typeof schema !== 'object' || schema === null)
|
|
37
|
+
return 'unknown';
|
|
38
|
+
const node = schema;
|
|
39
|
+
if (Array.isArray(node['enum'])) {
|
|
40
|
+
return node['enum'].map((v) => JSON.stringify(v)).join(' | ');
|
|
41
|
+
}
|
|
42
|
+
const variants = ((_a = node['oneOf']) !== null && _a !== void 0 ? _a : node['anyOf']);
|
|
43
|
+
if (Array.isArray(variants)) {
|
|
44
|
+
return variants.map((v) => tsType(v, indent)).join(' | ');
|
|
45
|
+
}
|
|
46
|
+
const type = node['type'];
|
|
47
|
+
if (Array.isArray(type)) {
|
|
48
|
+
return type.map((t) => tsType(Object.assign(Object.assign({}, node), { type: t }), indent)).join(' | ');
|
|
49
|
+
}
|
|
50
|
+
switch (type) {
|
|
51
|
+
case 'string':
|
|
52
|
+
return 'string';
|
|
53
|
+
case 'number':
|
|
54
|
+
case 'integer':
|
|
55
|
+
return 'number';
|
|
56
|
+
case 'boolean':
|
|
57
|
+
return 'boolean';
|
|
58
|
+
case 'null':
|
|
59
|
+
return 'null';
|
|
60
|
+
case 'array':
|
|
61
|
+
return `${wrapForArray(tsType(node['items'], indent))}[]`;
|
|
62
|
+
case 'object':
|
|
63
|
+
case undefined: {
|
|
64
|
+
const properties = node['properties'];
|
|
65
|
+
if (typeof properties !== 'object' || properties === null) {
|
|
66
|
+
return type === 'object' ? 'Record<string, unknown>' : 'unknown';
|
|
67
|
+
}
|
|
68
|
+
const required = new Set(Array.isArray(node['required']) ? node['required'] : []);
|
|
69
|
+
const inner = indent + ' ';
|
|
70
|
+
const lines = Object.entries(properties).map(([key, value]) => {
|
|
71
|
+
const safeKey = IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
72
|
+
const optional = required.size > 0 && !required.has(key) ? '?' : '';
|
|
73
|
+
return `${inner}${safeKey}${optional}: ${tsType(value, inner)};`;
|
|
74
|
+
});
|
|
75
|
+
return `{\n${lines.join('\n')}\n${indent}}`;
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function wrapForArray(rendered) {
|
|
82
|
+
// A top-level union needs parentheses before `[]`; an object literal does
|
|
83
|
+
// not, even when one of its members is a union.
|
|
84
|
+
return rendered.startsWith('{') || !rendered.includes('|') ? rendered : `(${rendered})`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Renders the generated module: one frozen interface per documented version,
|
|
88
|
+
* a `Current` alias, and the document itself as a typed const.
|
|
89
|
+
*/
|
|
90
|
+
export function generateContractModule(rawDoc, options = {}) {
|
|
91
|
+
var _a, _b, _c;
|
|
92
|
+
const doc = assertDocLike(rawDoc);
|
|
93
|
+
const prefix = (_a = options.typePrefix) !== null && _a !== void 0 ? _a : pascalCase(doc.name);
|
|
94
|
+
const constName = (_b = options.constName) !== null && _b !== void 0 ? _b : `${camelCase(doc.name)}Contract`;
|
|
95
|
+
const sections = [];
|
|
96
|
+
sections.push(`/**`, ` * GENERATED by skew-contract gen from contract "${doc.name}" — do not edit.`, ` *`, ` * Each version below is a frozen snapshot of a shape that shipped. Editing`, ` * one changes what every old migration means; regenerate from the contract`, ` * document instead.`, ` */`, ``);
|
|
97
|
+
const schemas = (_c = doc.schemas) !== null && _c !== void 0 ? _c : {};
|
|
98
|
+
const documentedVersions = Object.keys(schemas)
|
|
99
|
+
.map((key) => Number(key))
|
|
100
|
+
.filter((v) => Number.isInteger(v) && v >= 1)
|
|
101
|
+
.sort((a, b) => a - b);
|
|
102
|
+
for (const versionNumber of documentedVersions) {
|
|
103
|
+
const schema = schemas[String(versionNumber)];
|
|
104
|
+
const step = doc.steps.find((s) => s.to === versionNumber);
|
|
105
|
+
if (step)
|
|
106
|
+
sections.push(`/** v${versionNumber} — ${step.description} */`);
|
|
107
|
+
else
|
|
108
|
+
sections.push(`/** v${versionNumber} — the base shape. */`);
|
|
109
|
+
sections.push(`export interface ${prefix}V${versionNumber} ${tsType(schema, '')}`, ``);
|
|
110
|
+
}
|
|
111
|
+
const currentVersion = documentedVersions.includes(doc.current) ? doc.current : null;
|
|
112
|
+
if (currentVersion !== null) {
|
|
113
|
+
sections.push(`/** The shape this contract currently serves. */`, `export type ${prefix} = ${prefix}V${currentVersion};`, ``);
|
|
114
|
+
}
|
|
115
|
+
sections.push(`/** The contract document, verbatim. Feed it to \`versionedFromContract\`. */`, `export const ${constName} = ${JSON.stringify(rawDoc, null, 2)} as const;`, ``);
|
|
116
|
+
return sections.join('\n');
|
|
117
|
+
}
|
|
118
|
+
/** Reads a contract JSON file and writes the generated module beside your source. */
|
|
119
|
+
export function generateContractFile(options) {
|
|
120
|
+
const raw = JSON.parse(readFileSync(options.in, 'utf8'));
|
|
121
|
+
const rendered = generateContractModule(raw, options);
|
|
122
|
+
mkdirSync(dirname(options.out), { recursive: true });
|
|
123
|
+
writeFileSync(options.out, rendered);
|
|
124
|
+
return { name: raw.name, out: options.out };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=contract-gen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-gen.js","sourceRoot":"","sources":["../../../../../libs/build/src/lib/contract-gen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build identity, and the manifest an origin serves so clients can discover it.
|
|
3
|
+
*
|
|
4
|
+
* Identity is generated as a *file*, not a bundler `define`. That keeps it
|
|
5
|
+
* portable across Angular CLI, Vite, and webpack, and — more usefully — makes
|
|
6
|
+
* it readable in tests and Node without running a build at all.
|
|
7
|
+
*/
|
|
8
|
+
export interface StampOptions {
|
|
9
|
+
/** Where to write the generated TypeScript constants. */
|
|
10
|
+
readonly out: string;
|
|
11
|
+
/** Where to write the JSON manifest the origin will serve. */
|
|
12
|
+
readonly manifest?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Directory to scan for emitted chunks. Present, the manifest gains a
|
|
15
|
+
* `modules` map, which is what lets a client distinguish "this route moved"
|
|
16
|
+
* from "this route was deleted".
|
|
17
|
+
*/
|
|
18
|
+
readonly assetDir?: string;
|
|
19
|
+
/** Overrides the derived id. CI should pass its commit SHA. */
|
|
20
|
+
readonly buildId?: string;
|
|
21
|
+
/** Overrides the timestamp. Mainly for reproducible tests. */
|
|
22
|
+
readonly builtAt?: string;
|
|
23
|
+
readonly cwd?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface StampResult {
|
|
26
|
+
readonly buildId: string;
|
|
27
|
+
readonly builtAt: string;
|
|
28
|
+
readonly modules: Record<string, {
|
|
29
|
+
file: string;
|
|
30
|
+
hash?: string;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolves a build id.
|
|
35
|
+
*
|
|
36
|
+
* Order matters: an explicit id wins, then CI's `SKEW_BUILD_ID`, then the git
|
|
37
|
+
* SHA (stable, meaningful, and identical across parallel CI jobs building the
|
|
38
|
+
* same commit), then a random value as a last resort.
|
|
39
|
+
*
|
|
40
|
+
* The random fallback is deliberately *not* a content hash of the output —
|
|
41
|
+
* two machines building the same commit should agree, and a content hash of a
|
|
42
|
+
* directory containing timestamps would not.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveBuildId(explicit?: string, cwd?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Scans an output directory for JavaScript chunks.
|
|
47
|
+
*
|
|
48
|
+
* Best-effort by design. Without a bundler metafile there is no way to recover
|
|
49
|
+
* the *logical* module a chunk came from, so entries are keyed by their
|
|
50
|
+
* emitted basename. Consumers that need logical ids supply them explicitly at
|
|
51
|
+
* the call site (see `lazy()` in `@braidlabs/angular-router`).
|
|
52
|
+
*/
|
|
53
|
+
export declare function scanAssets(assetDir: string): Record<string, {
|
|
54
|
+
file: string;
|
|
55
|
+
hash?: string;
|
|
56
|
+
}>;
|
|
57
|
+
/** Generates the identity module and, optionally, the manifest. */
|
|
58
|
+
export declare function stamp(options: StampOptions): StampResult;
|
package/src/lib/stamp.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a build id.
|
|
7
|
+
*
|
|
8
|
+
* Order matters: an explicit id wins, then CI's `SKEW_BUILD_ID`, then the git
|
|
9
|
+
* SHA (stable, meaningful, and identical across parallel CI jobs building the
|
|
10
|
+
* same commit), then a random value as a last resort.
|
|
11
|
+
*
|
|
12
|
+
* The random fallback is deliberately *not* a content hash of the output —
|
|
13
|
+
* two machines building the same commit should agree, and a content hash of a
|
|
14
|
+
* directory containing timestamps would not.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveBuildId(explicit, cwd = process.cwd()) {
|
|
17
|
+
if (explicit)
|
|
18
|
+
return explicit;
|
|
19
|
+
const fromEnv = process.env['SKEW_BUILD_ID'];
|
|
20
|
+
if (fromEnv)
|
|
21
|
+
return fromEnv;
|
|
22
|
+
try {
|
|
23
|
+
return execSync('git rev-parse --short HEAD', {
|
|
24
|
+
cwd,
|
|
25
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch (_a) {
|
|
30
|
+
return createHash('sha256').update(String(Date.now())).digest('hex').slice(0, 10);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Scans an output directory for JavaScript chunks.
|
|
35
|
+
*
|
|
36
|
+
* Best-effort by design. Without a bundler metafile there is no way to recover
|
|
37
|
+
* the *logical* module a chunk came from, so entries are keyed by their
|
|
38
|
+
* emitted basename. Consumers that need logical ids supply them explicitly at
|
|
39
|
+
* the call site (see `lazy()` in `@braidlabs/angular-router`).
|
|
40
|
+
*/
|
|
41
|
+
export function scanAssets(assetDir) {
|
|
42
|
+
const modules = {};
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = walk(assetDir);
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
return modules;
|
|
49
|
+
}
|
|
50
|
+
for (const abs of entries) {
|
|
51
|
+
if (!abs.endsWith('.js') && !abs.endsWith('.mjs'))
|
|
52
|
+
continue;
|
|
53
|
+
const file = relative(assetDir, abs).split(sep).join('/');
|
|
54
|
+
// "chunk-ABC123.js" and "main-XYZ.js" → logical key without the hash
|
|
55
|
+
const base = file.replace(/^.*\//, '');
|
|
56
|
+
const key = base.replace(/[.-][A-Za-z0-9_]{6,}\.m?js$/, '').replace(/\.m?js$/, '');
|
|
57
|
+
modules[key] = { file };
|
|
58
|
+
}
|
|
59
|
+
return modules;
|
|
60
|
+
}
|
|
61
|
+
function walk(dir) {
|
|
62
|
+
const out = [];
|
|
63
|
+
for (const name of readdirSync(dir)) {
|
|
64
|
+
const abs = join(dir, name);
|
|
65
|
+
if (statSync(abs).isDirectory())
|
|
66
|
+
out.push(...walk(abs));
|
|
67
|
+
else
|
|
68
|
+
out.push(abs);
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/** Generates the identity module and, optionally, the manifest. */
|
|
73
|
+
export function stamp(options) {
|
|
74
|
+
var _a, _b;
|
|
75
|
+
const cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
|
|
76
|
+
const buildId = resolveBuildId(options.buildId, cwd);
|
|
77
|
+
const builtAt = (_b = options.builtAt) !== null && _b !== void 0 ? _b : new Date().toISOString();
|
|
78
|
+
const modules = options.assetDir ? scanAssets(join(cwd, options.assetDir)) : {};
|
|
79
|
+
const source = `// Generated by @braidlabs/build — do not edit.
|
|
80
|
+
// Regenerated on every build; safe to gitignore.
|
|
81
|
+
export const BUILD_ID = ${JSON.stringify(buildId)};
|
|
82
|
+
export const BUILT_AT = ${JSON.stringify(builtAt)};
|
|
83
|
+
export const BUILD_IDENTITY = { buildId: BUILD_ID, builtAt: BUILT_AT } as const;
|
|
84
|
+
`;
|
|
85
|
+
writeFileTree(join(cwd, options.out), source);
|
|
86
|
+
if (options.manifest) {
|
|
87
|
+
const manifest = Object.assign({ buildId,
|
|
88
|
+
builtAt }, (Object.keys(modules).length > 0 ? { modules } : {}));
|
|
89
|
+
writeFileTree(join(cwd, options.manifest), JSON.stringify(manifest, null, 2) + '\n');
|
|
90
|
+
}
|
|
91
|
+
return { buildId, builtAt, modules };
|
|
92
|
+
}
|
|
93
|
+
function writeFileTree(path, contents) {
|
|
94
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
95
|
+
writeFileSync(path, contents, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=stamp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp.js","sourceRoot":"","sources":["../../../../../libs/build/src/lib/stamp.ts"],"names":[],"mappings":"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"}
|