Skip to content

Commit 1cb2ca8

Browse files
committed
Fix ESM output so plain Node can import the published package
The build used moduleResolution "bundler", so tsc emitted extensionless relative imports and JSON imports without import attributes. Node's ESM loader rejects both (ERR_MODULE_NOT_FOUND, ERR_IMPORT_ATTRIBUTE_MISSING), making dist/ unusable without a bundler. - Switch tsconfig to module/moduleResolution NodeNext, which enforces correct specifiers at build time - Add .js extensions to relative import/export specifiers in src/ - Add with { type: 'json' } to the seven schema imports in src/schemas.ts - Add a CI smoke test that packs the artifact, installs it into a clean project, and imports it with plain Node No API changes; all 121 tests pass and the packed tarball now imports cleanly on Node with all 38 exports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Z9YCiR6gQ6TZP5MWRcRD9
1 parent bf28d4b commit 1cb2ca8

12 files changed

Lines changed: 57 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ jobs:
2121
- run: npm ci
2222
- run: npm test
2323
- run: npm run build
24+
- name: ESM smoke test (import packed artifact with plain Node)
25+
run: |
26+
npm pack --pack-destination /tmp
27+
mkdir -p /tmp/smoke && cd /tmp/smoke && npm init -y
28+
npm install /tmp/dosipas-ts-*.tgz
29+
node -e "import('dosipas-ts').then(m => {
30+
if (typeof m.decodeTicket !== 'function') process.exit(1);
31+
console.log('ESM smoke test ok');
32+
})"

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@
1515

1616
### Bug Fixes
1717

18+
- **Published `dist/` is now importable by plain Node** (no bundler required).
19+
The build previously used `moduleResolution: "bundler"`, which emitted
20+
extensionless relative imports (`./decoder`) and JSON imports without import
21+
attributes — both rejected by Node's ESM loader (`ERR_MODULE_NOT_FOUND`,
22+
`ERR_IMPORT_ATTRIBUTE_MISSING`). The compiler now uses
23+
`module`/`moduleResolution: "NodeNext"`, relative specifiers carry `.js`
24+
extensions, and the schema imports use `with { type: 'json' }`. CI now packs
25+
the artifact and imports it with plain Node so this cannot silently regress.
1826
- **CLI `decode-ticket.ts`**: Fixed crash caused by references to removed types (`SecurityInfo`, `RailTicketData`) and nonexistent properties (`ticket.security`, `ticket.railTickets`, etc.). The CLI now uses the actual `UicBarcodeTicket` type hierarchy (`level2SignedData.level1Data`, `dataSequence[].decoded`, etc.). Also added computed timestamp display (issuing time, end of validity, dynamic content time).
1927

2028
### Maintenance

src/control.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
* and running a series of focused check functions — each responsible for
66
* verifying one specific aspect of the ticket.
77
*/
8-
import { decodeTicket } from './decoder';
9-
import { verifyLevel1Signature, verifyLevel2Signature } from './verifier';
10-
import { getEndOfValidityTime, getDynamicContentTime, getOpenTicketValidityWindow } from './time-helpers';
8+
import { decodeTicket } from './decoder.js';
9+
import { verifyLevel1Signature, verifyLevel2Signature } from './verifier.js';
10+
import { getEndOfValidityTime, getDynamicContentTime, getOpenTicketValidityWindow } from './time-helpers.js';
1111
import type {
1212
UicBarcodeTicket,
1313
UicRailTicketData,
@@ -17,7 +17,7 @@ import type {
1717
ControlResult,
1818
ControlOptions,
1919
OpenTicketData,
20-
} from './types';
20+
} from './types.js';
2121

2222
// ---------------------------------------------------------------------------
2323
// Hex helpers

src/decoder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
type SchemaNode,
1414
} from 'asn1-per-ts';
1515
import type { Codec } from 'asn1-per-ts';
16-
import { HEADER_SCHEMAS, RAIL_TICKET_SCHEMAS, INTERCODE_SCHEMAS, DYNAMIC_CONTENT_SCHEMAS } from './schemas';
16+
import { HEADER_SCHEMAS, RAIL_TICKET_SCHEMAS, INTERCODE_SCHEMAS, DYNAMIC_CONTENT_SCHEMAS } from './schemas.js';
1717
import type {
1818
UicBarcodeTicket,
1919
UicRailTicketData,
@@ -27,7 +27,7 @@ import type {
2727
Level1Data,
2828
DataSequenceEntry,
2929
Level2Data,
30-
} from './types';
30+
} from './types.js';
3131

3232
// ---------------------------------------------------------------------------
3333
// Codec caches

src/encoder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import {
1111
type SchemaNode,
1212
} from 'asn1-per-ts';
1313
import type { Codec, RawBytes } from 'asn1-per-ts';
14-
import { HEADER_SCHEMAS, RAIL_TICKET_SCHEMAS, INTERCODE_SCHEMAS, DYNAMIC_CONTENT_SCHEMAS } from './schemas';
14+
import { HEADER_SCHEMAS, RAIL_TICKET_SCHEMAS, INTERCODE_SCHEMAS, DYNAMIC_CONTENT_SCHEMAS } from './schemas.js';
1515
import type {
1616
UicBarcodeTicket,
1717
Level1Data,
1818
Level2Data,
1919
UicRailTicketData,
2020
UicDynamicContentData,
2121
IntercodeDynamicData,
22-
} from './types';
22+
} from './types.js';
2323

2424
// ---------------------------------------------------------------------------
2525
// Codec caches (separate from decoder to avoid coupling)

src/index.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
export { decodeTicket, decodeTicketFromBytes } from './decoder';
2-
export { encodeTicket, encodeTicketToBytes, encodeLevel2Data, encodeLevel1Data, encodeLevel2SignedData, encodeUicBarcode } from './encoder';
3-
export { verifySignatures, verifyLevel1Signature, verifyLevel2Signature, findKeyInXml, parseKeysXml } from './verifier';
4-
export { controlTicket } from './control';
5-
export { getIssuingTime, getEndOfValidityTime, getDynamicContentTime, getOpenTicketValidityWindow } from './time-helpers';
6-
export { extractSignedData } from './signed-data';
7-
export { signLevel1, signLevel2, signAndEncodeTicket, signPayload, generateKeyPair, getPublicKey, CURVES } from './signer';
8-
export { SAMPLE_TICKET_HEX, SNCF_TER_TICKET_HEX, SOLEA_TICKET_HEX, BUS_ARDECHE_TICKET_HEX, BUS_AIN_TICKET_HEX, DROME_BUS_TICKET_HEX, CTS_TICKET_HEX, GRAND_EST_U1_FCB3_HEX } from './fixtures';
9-
export { SNCF_TER_SIGNATURES, SOLEA_SIGNATURES, CTS_SIGNATURES } from './signature-fixtures';
1+
export { decodeTicket, decodeTicketFromBytes } from './decoder.js';
2+
export { encodeTicket, encodeTicketToBytes, encodeLevel2Data, encodeLevel1Data, encodeLevel2SignedData, encodeUicBarcode } from './encoder.js';
3+
export { verifySignatures, verifyLevel1Signature, verifyLevel2Signature, findKeyInXml, parseKeysXml } from './verifier.js';
4+
export { controlTicket } from './control.js';
5+
export { getIssuingTime, getEndOfValidityTime, getDynamicContentTime, getOpenTicketValidityWindow } from './time-helpers.js';
6+
export { extractSignedData } from './signed-data.js';
7+
export { signLevel1, signLevel2, signAndEncodeTicket, signPayload, generateKeyPair, getPublicKey, CURVES } from './signer.js';
8+
export { SAMPLE_TICKET_HEX, SNCF_TER_TICKET_HEX, SOLEA_TICKET_HEX, BUS_ARDECHE_TICKET_HEX, BUS_AIN_TICKET_HEX, DROME_BUS_TICKET_HEX, CTS_TICKET_HEX, GRAND_EST_U1_FCB3_HEX } from './fixtures.js';
9+
export { SNCF_TER_SIGNATURES, SOLEA_SIGNATURES, CTS_SIGNATURES } from './signature-fixtures.js';
1010

1111
export type {
1212
UicBarcodeTicket,
@@ -46,10 +46,10 @@ export type {
4646
ZoneData,
4747
LineData,
4848
ViaStationData,
49-
} from './types';
49+
} from './types.js';
5050

51-
export type { UicPublicKeyEntry } from './verifier';
52-
export type { ExtractedSignedData } from './signed-data';
53-
export type { CurveName, CurveConfig, SigningKeyPair } from './signer';
51+
export type { UicPublicKeyEntry } from './verifier.js';
52+
export type { ExtractedSignedData } from './signed-data.js';
53+
export type { CurveName, CurveConfig, SigningKeyPair } from './signer.js';
5454

5555
export { RawBytes } from 'asn1-per-ts';

src/schemas.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
*/
55
import type { SchemaNode } from 'asn1-per-ts';
66

7-
import headerV1 from '../schemas/uic-barcode/uicBarcodeHeader_v1.schema.json';
8-
import headerV2 from '../schemas/uic-barcode/uicBarcodeHeader_v2.schema.json';
9-
import railTicketV1 from '../schemas/uic-barcode/uicRailTicketData_v1.schema.json';
10-
import railTicketV2 from '../schemas/uic-barcode/uicRailTicketData_v2.schema.json';
11-
import railTicketV3 from '../schemas/uic-barcode/uicRailTicketData_v3.schema.json';
12-
import intercode6 from '../schemas/uic-barcode/intercode6.schema.json';
13-
import dynamicContentV1 from '../schemas/uic-barcode/uicDynamicContentData_v1.schema.json';
7+
import headerV1 from '../schemas/uic-barcode/uicBarcodeHeader_v1.schema.json' with { type: 'json' };
8+
import headerV2 from '../schemas/uic-barcode/uicBarcodeHeader_v2.schema.json' with { type: 'json' };
9+
import railTicketV1 from '../schemas/uic-barcode/uicRailTicketData_v1.schema.json' with { type: 'json' };
10+
import railTicketV2 from '../schemas/uic-barcode/uicRailTicketData_v2.schema.json' with { type: 'json' };
11+
import railTicketV3 from '../schemas/uic-barcode/uicRailTicketData_v3.schema.json' with { type: 'json' };
12+
import intercode6 from '../schemas/uic-barcode/intercode6.schema.json' with { type: 'json' };
13+
import dynamicContentV1 from '../schemas/uic-barcode/uicDynamicContentData_v1.schema.json' with { type: 'json' };
1414

1515
type SchemaMap = Record<string, SchemaNode>;
1616

src/signed-data.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
type SchemaNode,
1212
type DecodedNode,
1313
} from 'asn1-per-ts';
14-
import { HEADER_SCHEMAS } from './schemas';
14+
import { HEADER_SCHEMAS } from './schemas.js';
1515

1616
// ---------------------------------------------------------------------------
1717
// Codec cache (separate from decoder/encoder to avoid coupling)

src/signer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
*/
1313
import { p256, p384, p521 } from '@noble/curves/nist.js';
1414

15-
import { encodeLevel1Data, encodeLevel2Data, encodeLevel2SignedData, encodeUicBarcode, encodeTicketToBytes } from './encoder';
16-
import { extractSignedData } from './signed-data';
17-
import { rawToDer } from './signature-utils';
15+
import { encodeLevel1Data, encodeLevel2Data, encodeLevel2SignedData, encodeUicBarcode, encodeTicketToBytes } from './encoder.js';
16+
import { extractSignedData } from './signed-data.js';
17+
import { rawToDer } from './signature-utils.js';
1818
import type { RawBytes } from 'asn1-per-ts';
19-
import type { UicBarcodeTicket, Level1Data } from './types';
19+
import type { UicBarcodeTicket, Level1Data } from './types.js';
2020

2121
// ---------------------------------------------------------------------------
2222
// Types

src/time-helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type {
1111
IntercodeDynamicData,
1212
OpenTicketData,
1313
IssuingDetail,
14-
} from './types';
14+
} from './types.js';
1515

1616
// ---------------------------------------------------------------------------
1717
// Internal accessors

0 commit comments

Comments
 (0)