diff --git a/src/compile.ts b/src/compile.ts index b5356c23..3fce96df 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -1,7 +1,8 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { exec } from './util'; +import { exec, validateOptions } from './util'; import { Options } from './options'; +import * as crypto from 'crypto'; const compilerModule = require.resolve('jsii/bin/jsii'); @@ -9,6 +10,8 @@ const compilerModule = require.resolve('jsii/bin/jsii'); * Compiles the source files in `workdir` with jsii. */ export async function compile(workdir: string, options: Options) { + validateOptions(options); + const args = [ '--silence-warnings', 'reserved-word' ]; const entrypoint = options.entrypoint ?? 'index.ts'; @@ -23,6 +26,8 @@ export async function compile(workdir: string, options: Options) { // path to entrypoint without extension const basepath = path.join(path.dirname(entrypoint), path.basename(entrypoint, '.ts')); + const moduleKey = options.moduleKey?.replace(/\./g, '').replace(/\//g, '') ?? crypto.createHash('sha256').update(basepath, 'utf8').digest('hex'); + // jsii modules to include const moduleDirs = options.deps ?? []; @@ -46,9 +51,8 @@ export async function compile(workdir: string, options: Options) { } } - const pkg = { - name: 'generated', + name: moduleKey, version: '0.0.0', author: 'generated@generated.com', main: `${basepath}.js`, diff --git a/src/options.ts b/src/options.ts index eb34528f..7642740a 100644 --- a/src/options.ts +++ b/src/options.ts @@ -18,6 +18,15 @@ export interface Options { */ jsii?: JsiiOutputOptions; + /** + * Key for the module to prevent JSII collisions. + * + * Use your own if it's project-unique, otherwise use default. + * + * @default - hash of the basepath to the module + */ + moduleKey?: string + /** * Produce python code. * @default - python is not generated @@ -47,6 +56,9 @@ export interface PythonOutputOptions { /** * The name of the the python module to generate. + * + * This must follow the standard Python module name conventions. + * For example, it cannot include a hyphen ('-') */ moduleName: string; } @@ -59,6 +71,9 @@ export interface JavaOutputOptions { /** * The name of the java package to generate + * + * This must follow standard Java package conventions. + * For example, it cannot include a hyphen ('-') */ package: string; } diff --git a/src/util.ts b/src/util.ts index 885112ee..8eda36d6 100644 --- a/src/util.ts +++ b/src/util.ts @@ -2,6 +2,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as os from 'os'; import { spawn, SpawnOptions } from 'child_process'; +import { Options } from './options'; export async function mkdtemp(closure: (dir: string) => Promise) { const workdir = await fs.mkdtemp(path.join(os.tmpdir(), 'temp-')); @@ -55,3 +56,20 @@ export async function exec(moduleName: string, args: string[] = [], options: Spa }); }); } + +/** + * This validates that the Python module name and Java package name + * conform to language-specific constraints. + * + * @param options Options set by the consumer + * @throws error if options do not conform + */ +export function validateOptions(options: Options) { + if (options.python?.moduleName.includes('-')) { + throw new Error(`Python moduleName [${options.python.moduleName}] may not contain "-"`); + } + + if (options.java?.package.includes('-')) { + throw new Error(`Java package [${options.java.package}] may not contain "-"`); + } +} diff --git a/test/__snapshots__/cli.test.ts.snap b/test/__snapshots__/cli.test.ts.snap index cc2ae5ff..fa889860 100644 --- a/test/__snapshots__/cli.test.ts.snap +++ b/test/__snapshots__/cli.test.ts.snap @@ -45,7 +45,7 @@ public final class $Module extends JsiiModule { private final Map> cache = new HashMap<>(); public $Module() { - super(\\"generated\\", \\"0.0.0\\", $Module.class, \\"generated@0.0.0.jsii.tgz\\"); + super(\\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060\\", \\"0.0.0\\", $Module.class, \\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060@0.0.0.jsii.tgz\\"); } @Override @@ -76,7 +76,7 @@ public final class $Module extends JsiiModule { * A sophisticaed multi-language calculator. */ -@software.amazon.jsii.Jsii(module = mypackage.$Module.class, fqn = \\"generated.Calculator\\") +@software.amazon.jsii.Jsii(module = mypackage.$Module.class, fqn = \\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Calculator\\") public class Calculator extends software.amazon.jsii.JsiiObject { protected Calculator(final software.amazon.jsii.JsiiObjectRef objRef) { @@ -126,7 +126,7 @@ public class Calculator extends software.amazon.jsii.JsiiObject { * Math operands. */ -@software.amazon.jsii.Jsii(module = mypackage.$Module.class, fqn = \\"generated.Operands\\") +@software.amazon.jsii.Jsii(module = mypackage.$Module.class, fqn = \\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands\\") @software.amazon.jsii.Jsii.Proxy(Operands.Jsii$Proxy.class) public interface Operands extends software.amazon.jsii.JsiiSerializable { @@ -229,7 +229,7 @@ public interface Operands extends software.amazon.jsii.JsiiSerializable { data.set(\\"rhs\\", om.valueToTree(this.getRhs())); final com.fasterxml.jackson.databind.node.ObjectNode struct = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); - struct.set(\\"fqn\\", om.valueToTree(\\"generated.Operands\\")); + struct.set(\\"fqn\\", om.valueToTree(\\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands\\")); struct.set(\\"data\\", data); final com.fasterxml.jackson.databind.node.ObjectNode obj = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); @@ -258,8 +258,8 @@ public interface Operands extends software.amazon.jsii.JsiiSerializable { } } ", - "src/main/resources/mypackage/$Module.txt": "generated.Calculator=mypackage.Calculator -generated.Operands=mypackage.Operands + "src/main/resources/mypackage/$Module.txt": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Calculator=mypackage.Calculator +4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands=mypackage.Operands ", } `; @@ -272,12 +272,12 @@ Object { "author", ], }, - "description": "generated", - "fingerprint": "+fSgo9PQwFAKmX2JcaRILwTXE/p1H1bq+QKuOREAfuE=", + "description": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060", + "fingerprint": "64TWv4C2KsEvkRVfUjD1gQTbNC2rsTlISDNv4F9JUw0=", "homepage": "http://generated", "jsiiVersion": "1.6.0 (build 248e75b)", "license": "Apache-2.0", - "name": "generated", + "name": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060", "repository": Object { "type": "git", "url": "http://generated", @@ -285,16 +285,16 @@ Object { "schema": "jsii/0.10.0", "targets": Object { "js": Object { - "npm": "generated", + "npm": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060", }, }, "types": Object { - "generated.Calculator": Object { - "assembly": "generated", + "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Calculator": Object { + "assembly": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060", "docs": Object { "summary": "A sophisticaed multi-language calculator.", }, - "fqn": "generated.Calculator", + "fqn": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Calculator", "initializer": Object {}, "kind": "class", "locationInModule": Object { @@ -318,7 +318,7 @@ Object { }, "name": "ops", "type": Object { - "fqn": "generated.Operands", + "fqn": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands", }, }, ], @@ -344,7 +344,7 @@ Object { }, "name": "ops", "type": Object { - "fqn": "generated.Operands", + "fqn": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands", }, }, ], @@ -370,7 +370,7 @@ Object { }, "name": "ops", "type": Object { - "fqn": "generated.Operands", + "fqn": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands", }, }, ], @@ -383,13 +383,13 @@ Object { ], "name": "Calculator", }, - "generated.Operands": Object { - "assembly": "generated", + "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands": Object { + "assembly": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060", "datatype": true, "docs": Object { "summary": "Math operands.", }, - "fqn": "generated.Operands", + "fqn": "4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands", "kind": "interface", "locationInModule": Object { "filename": "lib/main.ts", @@ -449,7 +449,7 @@ import publication from ._jsii import * -class Calculator(metaclass=jsii.JSIIMeta, jsii_type=\\"generated.Calculator\\"): +class Calculator(metaclass=jsii.JSIIMeta, jsii_type=\\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Calculator\\"): \\"\\"\\"A sophisticaed multi-language calculator.\\"\\"\\" def __init__(self) -> None: jsii.create(Calculator, self, []) @@ -488,7 +488,7 @@ class Calculator(metaclass=jsii.JSIIMeta, jsii_type=\\"generated.Calculator\\"): return jsii.invoke(self, \\"sub\\", [ops]) -@jsii.data_type(jsii_type=\\"generated.Operands\\", jsii_struct_bases=[], name_mapping={'lhs': 'lhs', 'rhs': 'rhs'}) +@jsii.data_type(jsii_type=\\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060.Operands\\", jsii_struct_bases=[], name_mapping={'lhs': 'lhs', 'rhs': 'rhs'}) class Operands(): def __init__(self, *, lhs: jsii.Number, rhs: jsii.Number) -> None: \\"\\"\\"Math operands. @@ -538,7 +538,7 @@ import jsii import jsii.compat import publication -__jsii_assembly__ = jsii.JSIIAssembly.load(\\"generated\\", \\"0.0.0\\", __name__[0:-6], \\"generated@0.0.0.jsii.tgz\\") +__jsii_assembly__ = jsii.JSIIAssembly.load(\\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060\\", \\"0.0.0\\", __name__[0:-6], \\"4c6b576fbe7e27053874813d9754cb2e46811d806c1e0aa9aae8add4c3763060@0.0.0.jsii.tgz\\") __all__ = [ \\"__jsii_assembly__\\", diff --git a/test/__snapshots__/srcmak.test.ts.snap b/test/__snapshots__/srcmak.test.ts.snap index 00980e12..ff502f7e 100644 --- a/test/__snapshots__/srcmak.test.ts.snap +++ b/test/__snapshots__/srcmak.test.ts.snap @@ -45,7 +45,7 @@ public final class $Module extends JsiiModule { private final Map> cache = new HashMap<>(); public $Module() { - super(\\"generated\\", \\"0.0.0\\", $Module.class, \\"generated@0.0.0.jsii.tgz\\"); + super(\\"javapackage\\", \\"0.0.0\\", $Module.class, \\"javapackage@0.0.0.jsii.tgz\\"); } @Override @@ -73,7 +73,7 @@ public final class $Module extends JsiiModule { "src/main/java/hello/world/Hello.java": "package hello.world; -@software.amazon.jsii.Jsii(module = hello.world.$Module.class, fqn = \\"generated.Hello\\") +@software.amazon.jsii.Jsii(module = hello.world.$Module.class, fqn = \\"javapackage.Hello\\") public class Hello extends software.amazon.jsii.JsiiObject { protected Hello(final software.amazon.jsii.JsiiObjectRef objRef) { @@ -97,7 +97,7 @@ public class Hello extends software.amazon.jsii.JsiiObject { "src/main/java/hello/world/Operands.java": "package hello.world; -@software.amazon.jsii.Jsii(module = hello.world.$Module.class, fqn = \\"generated.Operands\\") +@software.amazon.jsii.Jsii(module = hello.world.$Module.class, fqn = \\"javapackage.Operands\\") @software.amazon.jsii.Jsii.Proxy(Operands.Jsii$Proxy.class) public interface Operands extends software.amazon.jsii.JsiiSerializable { @@ -194,7 +194,7 @@ public interface Operands extends software.amazon.jsii.JsiiSerializable { data.set(\\"rhs\\", om.valueToTree(this.getRhs())); final com.fasterxml.jackson.databind.node.ObjectNode struct = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); - struct.set(\\"fqn\\", om.valueToTree(\\"generated.Operands\\")); + struct.set(\\"fqn\\", om.valueToTree(\\"javapackage.Operands\\")); struct.set(\\"data\\", data); final com.fasterxml.jackson.databind.node.ObjectNode obj = com.fasterxml.jackson.databind.node.JsonNodeFactory.instance.objectNode(); @@ -223,8 +223,8 @@ public interface Operands extends software.amazon.jsii.JsiiSerializable { } } ", - "src/main/resources/hello/world/$Module.txt": "generated.Hello=hello.world.Hello -generated.Operands=hello.world.Operands + "src/main/resources/hello/world/$Module.txt": "javapackage.Hello=hello.world.Hello +javapackage.Operands=hello.world.Operands ", } `; @@ -237,12 +237,12 @@ Object { "author", ], }, - "description": "generated", - "fingerprint": "cNtb65kyZQOiBulmzr+bs6dCGdv4uhea1tFTkbRGJTw=", + "description": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "fingerprint": "i19np+HVFMpwiHENNJeGk6AC3MVvpkkG7VtDtf9ouXs=", "homepage": "http://generated", "jsiiVersion": "1.6.0 (build 248e75b)", "license": "Apache-2.0", - "name": "generated", + "name": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", "repository": Object { "type": "git", "url": "http://generated", @@ -250,13 +250,13 @@ Object { "schema": "jsii/0.10.0", "targets": Object { "js": Object { - "npm": "generated", + "npm": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", }, }, "types": Object { - "generated.Foo": Object { - "assembly": "generated", - "fqn": "generated.Foo", + "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6.Foo": Object { + "assembly": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6", + "fqn": "1bc04b5291c26a46d918139138b992d2de976d6851d0893b0476b85bfbdfc6e6.Foo", "initializer": Object {}, "kind": "class", "locationInModule": Object { @@ -300,7 +300,7 @@ import publication from ._jsii import * -class Hello(metaclass=jsii.JSIIMeta, jsii_type=\\"generated.Hello\\"): +class Hello(metaclass=jsii.JSIIMeta, jsii_type=\\"pythonpackage.Hello\\"): def __init__(self) -> None: jsii.create(Hello, self, []) @@ -315,7 +315,7 @@ class Hello(metaclass=jsii.JSIIMeta, jsii_type=\\"generated.Hello\\"): return jsii.invoke(self, \\"add\\", [ops]) -@jsii.data_type(jsii_type=\\"generated.Operands\\", jsii_struct_bases=[], name_mapping={'lhs': 'lhs', 'rhs': 'rhs'}) +@jsii.data_type(jsii_type=\\"pythonpackage.Operands\\", jsii_struct_bases=[], name_mapping={'lhs': 'lhs', 'rhs': 'rhs'}) class Operands(): def __init__(self, *, lhs: jsii.Number, rhs: jsii.Number) -> None: \\"\\"\\" @@ -362,7 +362,7 @@ import jsii import jsii.compat import publication -__jsii_assembly__ = jsii.JSIIAssembly.load(\\"generated\\", \\"0.0.0\\", __name__[0:-6], \\"generated@0.0.0.jsii.tgz\\") +__jsii_assembly__ = jsii.JSIIAssembly.load(\\"pythonpackage\\", \\"0.0.0\\", __name__[0:-6], \\"pythonpackage@0.0.0.jsii.tgz\\") __all__ = [ \\"__jsii_assembly__\\", diff --git a/test/cli.test.ts b/test/cli.test.ts index 377b5e33..13ccc28b 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -74,7 +74,7 @@ test('python output', async () => { ); expect(await snapshotDirectory(outdir, { - excludeFiles: [ 'generated@0.0.0.jsii.tgz' ], + excludeFiles: [ '@0.0.0.jsii.tgz' ], })).toMatchSnapshot(); }); }); @@ -92,7 +92,7 @@ test('java output', async () => { expect(await snapshotDirectory(outdir, { excludeLines: [ /.*@javax.annotation.Generated.*/ ], - excludeFiles: [ 'generated@0.0.0.jsii.tgz' ], + excludeFiles: [ '@0.0.0.jsii.tgz' ], })).toMatchSnapshot(); }); }); diff --git a/test/srcmak.test.ts b/test/srcmak.test.ts index 17e16879..ae30f1ba 100644 --- a/test/srcmak.test.ts +++ b/test/srcmak.test.ts @@ -61,6 +61,7 @@ test('python + different entrypoint + submodule', async () => { await mkdtemp(async target => { await srcmak(source, { entrypoint: 'different/entry.ts', + moduleKey: 'python.package', python: { outdir: target, moduleName: 'my_python_module.submodule', @@ -68,7 +69,7 @@ test('python + different entrypoint + submodule', async () => { }); const dir = await snapshotDirectory(target, { - excludeFiles: [ 'generated@0.0.0.jsii.tgz' ], + excludeFiles: [ '@0.0.0.jsii.tgz' ], }); expect(dir).toMatchSnapshot(); }); @@ -96,6 +97,7 @@ test('java + different entrypoint', async () => { await mkdtemp(async target => { await srcmak(source, { entrypoint: 'different/entry.ts', + moduleKey: 'java.package', java: { outdir: target, package: 'hello.world', @@ -104,7 +106,7 @@ test('java + different entrypoint', async () => { const dir = await snapshotDirectory(target, { excludeLines: [ /.*@javax.annotation.Generated.*/ ], - excludeFiles: [ 'generated@0.0.0.jsii.tgz' ], + excludeFiles: [ '@0.0.0.jsii.tgz' ], }); expect(dir).toMatchSnapshot(); }); @@ -146,3 +148,25 @@ test('outputJsii can be used to look at the jsii file', async () => { }); }) }); + +test('java with invalid package', async () => { + await expect(srcmak('.', { + entrypoint: 'different/entry.ts', + moduleKey: 'java.package', + java: { + outdir: '.', + package: 'hello-world', + }, + })).rejects.toEqual(new Error('Java package [hello-world] may not contain "-"')); +}); + +test('python with invalid module name', async () => { + await expect(srcmak('.', { + entrypoint: 'different/entry.ts', + moduleKey: 'python.package', + python: { + outdir: '.', + moduleName: 'my-python.submodule', + }, + })).rejects.toEqual(new Error('Python moduleName [my-python.submodule] may not contain "-"')); +}); \ No newline at end of file diff --git a/test/util.ts b/test/util.ts index 48e6b737..db15aad1 100644 --- a/test/util.ts +++ b/test/util.ts @@ -15,8 +15,14 @@ export async function snapshotDirectory(basedir: string, excludeOptions: Snapsho const absdir = path.join(basedir, reldir); const { excludeLines, excludeFiles } = excludeOptions; for (const file of await fs.readdir(absdir)) { - if (excludeFiles?.includes(file)) { - continue; // skip + let skip = false + excludeFiles?.forEach((excludeFile) => { + if (file.includes(excludeFile)) { + skip = true; + } + }); + if (skip) { + continue; } const abspath = path.join(absdir, file); diff --git a/yarn.lock b/yarn.lock index 8b77c331..decd333d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6035,4 +6035,4 @@ yargs@^15.3.1, yargs@^15.4.0: string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^18.1.2" + yargs-parser "^18.1.2" \ No newline at end of file