@zmdb/cli 1.0.0-beta.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/LICENSE +674 -0
- package/README.md +19 -0
- package/dist/__fixtures__/http-client/src/contract.d.ts +38 -0
- package/dist/__fixtures__/http-client/src/contract.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/contract.js +121 -0
- package/dist/__fixtures__/http-client/src/contract.js.map +1 -0
- package/dist/__fixtures__/http-client/src/models.d.ts +11 -0
- package/dist/__fixtures__/http-client/src/models.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/models.js +2 -0
- package/dist/__fixtures__/http-client/src/models.js.map +1 -0
- package/dist/__fixtures__/http-client/src/schema.d.ts +4 -0
- package/dist/__fixtures__/http-client/src/schema.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/schema.js +2 -0
- package/dist/__fixtures__/http-client/src/schema.js.map +1 -0
- package/dist/__fixtures__/http-client/src/unrelated.d.ts +2 -0
- package/dist/__fixtures__/http-client/src/unrelated.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/src/unrelated.js +2 -0
- package/dist/__fixtures__/http-client/src/unrelated.js.map +1 -0
- package/dist/__fixtures__/http-client/zmdb.config.d.ts +16 -0
- package/dist/__fixtures__/http-client/zmdb.config.d.ts.map +1 -0
- package/dist/__fixtures__/http-client/zmdb.config.js +12 -0
- package/dist/__fixtures__/http-client/zmdb.config.js.map +1 -0
- package/dist/__fixtures__/project/src/schema.d.ts +6 -0
- package/dist/__fixtures__/project/src/schema.d.ts.map +1 -0
- package/dist/__fixtures__/project/src/schema.js +2 -0
- package/dist/__fixtures__/project/src/schema.js.map +1 -0
- package/dist/__fixtures__/project/zmdb.config.d.ts +22 -0
- package/dist/__fixtures__/project/zmdb.config.d.ts.map +1 -0
- package/dist/__fixtures__/project/zmdb.config.js +55 -0
- package/dist/__fixtures__/project/zmdb.config.js.map +1 -0
- package/dist/application-loader.d.ts +4 -0
- package/dist/application-loader.d.ts.map +1 -0
- package/dist/application-loader.js +102 -0
- package/dist/application-loader.js.map +1 -0
- package/dist/args.d.ts +23 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +353 -0
- package/dist/args.js.map +1 -0
- package/dist/atomic.d.ts +13 -0
- package/dist/atomic.d.ts.map +1 -0
- package/dist/atomic.js +28 -0
- package/dist/atomic.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +7 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/client.d.ts +38 -0
- package/dist/commands/client.d.ts.map +1 -0
- package/dist/commands/client.js +347 -0
- package/dist/commands/client.js.map +1 -0
- package/dist/commands/codegen.d.ts +9 -0
- package/dist/commands/codegen.d.ts.map +1 -0
- package/dist/commands/codegen.js +56 -0
- package/dist/commands/codegen.js.map +1 -0
- package/dist/commands/embed.d.ts +8 -0
- package/dist/commands/embed.d.ts.map +1 -0
- package/dist/commands/embed.js +17 -0
- package/dist/commands/embed.js.map +1 -0
- package/dist/commands/export.d.ts +5 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +6 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/generate.d.ts +5 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +6 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/migrate.d.ts +9 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +18 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/new.d.ts +4 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +35 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/pull.d.ts +5 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +14 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +6 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +11 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/studio.d.ts +31 -0
- package/dist/commands/studio.d.ts.map +1 -0
- package/dist/commands/studio.js +154 -0
- package/dist/commands/studio.js.map +1 -0
- package/dist/commands/upgrade.d.ts +5 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +11 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/database.d.ts +4 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +8 -0
- package/dist/database.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +626 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +6 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +75 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/migration-project.d.ts +14 -0
- package/dist/migration-project.d.ts.map +1 -0
- package/dist/migration-project.js +74 -0
- package/dist/migration-project.js.map +1 -0
- package/dist/output.d.ts +27 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +60 -0
- package/dist/output.js.map +1 -0
- package/dist/repl.d.ts +48 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +265 -0
- package/dist/repl.js.map +1 -0
- package/dist/scaffold.d.ts +28 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +414 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/studio/index.d.ts +20 -0
- package/dist/studio/index.d.ts.map +1 -0
- package/dist/studio/index.js +607 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/templates/command.d.ts +3 -0
- package/dist/templates/command.d.ts.map +1 -0
- package/dist/templates/command.js +65 -0
- package/dist/templates/command.js.map +1 -0
- package/dist/templates/controller.d.ts +3 -0
- package/dist/templates/controller.d.ts.map +1 -0
- package/dist/templates/controller.js +41 -0
- package/dist/templates/controller.js.map +1 -0
- package/dist/templates/index.d.ts +4 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +18 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/templates/module.d.ts +3 -0
- package/dist/templates/module.d.ts.map +1 -0
- package/dist/templates/module.js +41 -0
- package/dist/templates/module.js.map +1 -0
- package/dist/templates/project.d.ts +3 -0
- package/dist/templates/project.d.ts.map +1 -0
- package/dist/templates/project.js +280 -0
- package/dist/templates/project.js.map +1 -0
- package/dist/templates/repository.d.ts +3 -0
- package/dist/templates/repository.d.ts.map +1 -0
- package/dist/templates/repository.js +66 -0
- package/dist/templates/repository.js.map +1 -0
- package/dist/templates/schema.d.ts +3 -0
- package/dist/templates/schema.d.ts.map +1 -0
- package/dist/templates/schema.js +36 -0
- package/dist/templates/schema.js.map +1 -0
- package/dist/templates/types.d.ts +25 -0
- package/dist/templates/types.d.ts.map +1 -0
- package/dist/templates/types.js +17 -0
- package/dist/templates/types.js.map +1 -0
- package/package.json +72 -0
- package/src/__fixtures__/http-client/package.json +5 -0
- package/src/__fixtures__/http-client/src/contract.ts +85 -0
- package/src/__fixtures__/http-client/src/models.ts +12 -0
- package/src/__fixtures__/http-client/src/schema.ts +3 -0
- package/src/__fixtures__/http-client/src/unrelated.ts +1 -0
- package/src/__fixtures__/http-client/tsconfig.json +17 -0
- package/src/__fixtures__/http-client/zmdb.config.ts +12 -0
- package/src/__fixtures__/project/package.json +5 -0
- package/src/__fixtures__/project/src/schema.ts +6 -0
- package/src/__fixtures__/project/tsconfig.json +24 -0
- package/src/__fixtures__/project/zmdb.config.ts +57 -0
- package/src/application-loader.ts +108 -0
- package/src/args.ts +391 -0
- package/src/atomic.ts +43 -0
- package/src/bin.ts +5 -0
- package/src/commands/check.ts +11 -0
- package/src/commands/client.ts +415 -0
- package/src/commands/codegen.ts +73 -0
- package/src/commands/embed.ts +36 -0
- package/src/commands/export.ts +10 -0
- package/src/commands/generate.ts +14 -0
- package/src/commands/migrate.ts +38 -0
- package/src/commands/new.ts +48 -0
- package/src/commands/pull.ts +32 -0
- package/src/commands/push.ts +21 -0
- package/src/commands/studio.ts +200 -0
- package/src/commands/upgrade.ts +14 -0
- package/src/database.ts +9 -0
- package/src/errors.ts +19 -0
- package/src/index.ts +710 -0
- package/src/lifecycle.ts +73 -0
- package/src/migration-project.ts +91 -0
- package/src/output.ts +77 -0
- package/src/repl.ts +328 -0
- package/src/scaffold.ts +485 -0
- package/src/studio/index.ts +674 -0
- package/src/templates/command.ts +66 -0
- package/src/templates/controller.ts +42 -0
- package/src/templates/index.ts +29 -0
- package/src/templates/module.ts +42 -0
- package/src/templates/project.ts +281 -0
- package/src/templates/repository.ts +67 -0
- package/src/templates/schema.ts +37 -0
- package/src/templates/types.ts +46 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const commandTemplate: TemplateFactory = ({ name }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: `src/${name.fileStem}.command.ts`,
|
|
7
|
+
source: `import { assert } from 'zmdb';
|
|
8
|
+
import { Command } from '@zmdb/app/commands';
|
|
9
|
+
|
|
10
|
+
export interface ${name.pascal}Args {
|
|
11
|
+
readonly dryRun?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export let ${name.camel}Runs = 0;
|
|
15
|
+
|
|
16
|
+
@Command<${name.pascal}Args>({
|
|
17
|
+
name: '${name.fileStem}',
|
|
18
|
+
description: 'Run the ${name.fileStem} command',
|
|
19
|
+
args: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
dryRun: { type: 'boolean' },
|
|
23
|
+
},
|
|
24
|
+
required: [],
|
|
25
|
+
},
|
|
26
|
+
validate: raw => assert<${name.pascal}Args>(raw),
|
|
27
|
+
})
|
|
28
|
+
export class ${name.pascal}Command {
|
|
29
|
+
run(_args: ${name.pascal}Args): number {
|
|
30
|
+
${name.camel}Runs += 1;
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
`,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: `src/${name.fileStem}.command.spec.ts`,
|
|
38
|
+
source: `import { createCommandApp } from '@zmdb/app/commands';
|
|
39
|
+
import { createTestApp } from 'zmdb/testing';
|
|
40
|
+
import { Module } from 'zmdb';
|
|
41
|
+
import { describe, expect, it } from 'vitest';
|
|
42
|
+
|
|
43
|
+
import {
|
|
44
|
+
${name.pascal}Command,
|
|
45
|
+
${name.camel}Runs,
|
|
46
|
+
} from './${name.fileStem}.command.js';
|
|
47
|
+
|
|
48
|
+
@Module({ commands: [${name.pascal}Command] })
|
|
49
|
+
class ${name.pascal}CommandTestModule {}
|
|
50
|
+
|
|
51
|
+
describe('${name.pascal}Command', () => {
|
|
52
|
+
it('runs through the command application', async () => {
|
|
53
|
+
await using testApp = createTestApp(${name.pascal}CommandTestModule);
|
|
54
|
+
await testApp.init();
|
|
55
|
+
|
|
56
|
+
await using commandApp = createCommandApp(${name.pascal}CommandTestModule);
|
|
57
|
+
await commandApp.init();
|
|
58
|
+
expect(await commandApp.run(['${name.fileStem}', '--dry-run'])).toBe(0);
|
|
59
|
+
expect(${name.camel}Runs).toBe(1);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
instructions: [`add to src/app.module.ts, in @Module({ commands: [ … ] }):\n ${name.pascal}Command,`],
|
|
66
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const controllerTemplate: TemplateFactory = ({ name }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: `src/${name.fileStem}.controller.ts`,
|
|
7
|
+
source: `import { Controller, Get } from 'zmdb';
|
|
8
|
+
|
|
9
|
+
@Controller('/${name.fileStem}')
|
|
10
|
+
export class ${name.pascal}Controller {
|
|
11
|
+
@Get()
|
|
12
|
+
list(): { readonly resource: string; readonly items: readonly unknown[] } {
|
|
13
|
+
return { resource: '${name.fileStem}', items: [] };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: `src/${name.fileStem}.controller.spec.ts`,
|
|
20
|
+
source: `import { Module } from 'zmdb';
|
|
21
|
+
import { createTestApp } from 'zmdb/testing';
|
|
22
|
+
import { bodyText } from 'zmdb/web';
|
|
23
|
+
import { describe, expect, it } from 'vitest';
|
|
24
|
+
|
|
25
|
+
import { ${name.pascal}Controller } from './${name.fileStem}.controller.js';
|
|
26
|
+
|
|
27
|
+
@Module({ controllers: [${name.pascal}Controller] })
|
|
28
|
+
class ${name.pascal}ControllerTestModule {}
|
|
29
|
+
|
|
30
|
+
describe('${name.pascal}Controller', () => {
|
|
31
|
+
it('serves its collection route', async () => {
|
|
32
|
+
await using app = createTestApp(${name.pascal}ControllerTestModule);
|
|
33
|
+
const response = await app.request({ method: 'GET', path: '/${name.fileStem}', headers: {} });
|
|
34
|
+
expect(response.status).toBe(200);
|
|
35
|
+
expect(JSON.parse(await bodyText(response))).toEqual({ resource: '${name.fileStem}', items: [] });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
instructions: [`add to src/app.module.ts, in @Module({ controllers: [ … ] }):\n ${name.pascal}Controller,`],
|
|
42
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { commandTemplate } from './command.js';
|
|
2
|
+
import { controllerTemplate } from './controller.js';
|
|
3
|
+
import { moduleTemplate } from './module.js';
|
|
4
|
+
import { projectTemplate } from './project.js';
|
|
5
|
+
import { repositoryTemplate } from './repository.js';
|
|
6
|
+
import { schemaTemplate } from './schema.js';
|
|
7
|
+
import type { ScaffoldKind, TemplateFactory } from './types.js';
|
|
8
|
+
|
|
9
|
+
const TEMPLATES: Readonly<Record<ScaffoldKind, TemplateFactory>> = {
|
|
10
|
+
command: commandTemplate,
|
|
11
|
+
controller: controllerTemplate,
|
|
12
|
+
module: moduleTemplate,
|
|
13
|
+
project: projectTemplate,
|
|
14
|
+
repository: repositoryTemplate,
|
|
15
|
+
schema: schemaTemplate,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function templateFor(kind: ScaffoldKind): TemplateFactory {
|
|
19
|
+
return TEMPLATES[kind];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
ScaffoldKind,
|
|
24
|
+
ScaffoldName,
|
|
25
|
+
TemplateContext,
|
|
26
|
+
TemplateFactory,
|
|
27
|
+
TemplateFile,
|
|
28
|
+
TemplatePlan,
|
|
29
|
+
} from './types.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const moduleTemplate: TemplateFactory = ({ name }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: `src/${name.fileStem}.module.ts`,
|
|
7
|
+
source: `import { Module } from 'zmdb';
|
|
8
|
+
import { createToken } from 'zmdb/web';
|
|
9
|
+
|
|
10
|
+
export interface ${name.pascal}Status {
|
|
11
|
+
readonly name: '${name.fileStem}';
|
|
12
|
+
readonly ready: true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ${name.constant}_STATUS = createToken<${name.pascal}Status>('${name.pascal}Status');
|
|
16
|
+
|
|
17
|
+
@Module({
|
|
18
|
+
providers: [{ token: ${name.constant}_STATUS, useValue: { name: '${name.fileStem}', ready: true } }],
|
|
19
|
+
exports: [${name.constant}_STATUS],
|
|
20
|
+
})
|
|
21
|
+
export class ${name.pascal}Module {}
|
|
22
|
+
`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: `src/${name.fileStem}.module.spec.ts`,
|
|
26
|
+
source: `import { createTestApp } from 'zmdb/testing';
|
|
27
|
+
import { describe, expect, it } from 'vitest';
|
|
28
|
+
|
|
29
|
+
import { ${name.constant}_STATUS, ${name.pascal}Module } from './${name.fileStem}.module.js';
|
|
30
|
+
|
|
31
|
+
describe('${name.pascal}Module', () => {
|
|
32
|
+
it('registers its status provider', async () => {
|
|
33
|
+
await using app = createTestApp(${name.pascal}Module);
|
|
34
|
+
const status = app.get(${name.constant}_STATUS);
|
|
35
|
+
expect(status).toEqual({ name: '${name.fileStem}', ready: true });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
instructions: [`add to src/app.module.ts, in @Module({ imports: [ … ] }):\n ${name.pascal}Module,`],
|
|
42
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { FORMAT_OPTIONS, type TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const projectTemplate: TemplateFactory = ({ name, packageVersion }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: 'package.json',
|
|
7
|
+
source: `${JSON.stringify({
|
|
8
|
+
name: name.fileStem,
|
|
9
|
+
private: true,
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
type: 'module',
|
|
12
|
+
scripts: {
|
|
13
|
+
build: 'node scripts/build.mjs app',
|
|
14
|
+
check:
|
|
15
|
+
'node scripts/build.mjs fmt-check && tsc --noEmit && oxlint . && node scripts/build.mjs test && vitest run --config vitest.config.ts',
|
|
16
|
+
fmt: 'node scripts/build.mjs fmt',
|
|
17
|
+
'fmt:check': 'node scripts/build.mjs fmt-check',
|
|
18
|
+
lint: 'oxlint .',
|
|
19
|
+
start: 'node dist/main.mjs',
|
|
20
|
+
test: 'node scripts/build.mjs test && vitest run --config vitest.config.ts',
|
|
21
|
+
typecheck: 'tsc --noEmit',
|
|
22
|
+
},
|
|
23
|
+
dependencies: {
|
|
24
|
+
'@zmdb/sqlite': packageVersion,
|
|
25
|
+
zmdb: packageVersion,
|
|
26
|
+
},
|
|
27
|
+
devDependencies: {
|
|
28
|
+
'@types/node': '26.4.1',
|
|
29
|
+
esbuild: '0.28.2',
|
|
30
|
+
oxfmt: '0.66.0',
|
|
31
|
+
oxlint: '1.81.0',
|
|
32
|
+
typescript: '7.0.2',
|
|
33
|
+
vitest: '5.0.0',
|
|
34
|
+
},
|
|
35
|
+
engines: {
|
|
36
|
+
node: '>=26',
|
|
37
|
+
},
|
|
38
|
+
})}\n`,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: 'tsconfig.json',
|
|
42
|
+
source: `${JSON.stringify({
|
|
43
|
+
compilerOptions: {
|
|
44
|
+
allowImportingTsExtensions: false,
|
|
45
|
+
exactOptionalPropertyTypes: true,
|
|
46
|
+
isolatedModules: true,
|
|
47
|
+
lib: ['ESNext', 'DOM', 'DOM.Iterable'],
|
|
48
|
+
module: 'NodeNext',
|
|
49
|
+
moduleResolution: 'NodeNext',
|
|
50
|
+
noEmit: true,
|
|
51
|
+
noImplicitOverride: true,
|
|
52
|
+
noUncheckedIndexedAccess: true,
|
|
53
|
+
skipLibCheck: true,
|
|
54
|
+
strict: true,
|
|
55
|
+
target: 'ESNext',
|
|
56
|
+
types: ['node'],
|
|
57
|
+
verbatimModuleSyntax: true,
|
|
58
|
+
},
|
|
59
|
+
include: ['src/**/*.ts', 'vitest.config.ts', 'zmdb.config.ts'],
|
|
60
|
+
})}\n`,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
path: 'scripts/build.mjs',
|
|
64
|
+
source: `import { glob, readFile, rm, writeFile } from 'node:fs/promises';
|
|
65
|
+
import { dirname, join } from 'node:path';
|
|
66
|
+
import { fileURLToPath } from 'node:url';
|
|
67
|
+
|
|
68
|
+
import { build } from 'esbuild';
|
|
69
|
+
import { format } from 'oxfmt';
|
|
70
|
+
import { zmdbAot } from 'zmdb/compiler';
|
|
71
|
+
|
|
72
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
73
|
+
const formatOptions = ${JSON.stringify(FORMAT_OPTIONS)};
|
|
74
|
+
|
|
75
|
+
async function formatProject(check) {
|
|
76
|
+
const paths = glob(['**/*.ts', '**/*.mjs', '**/*.json'], {
|
|
77
|
+
cwd: root,
|
|
78
|
+
exclude: ['**/node_modules/**', '**/dist/**', '**/generated-tests/**', '**/.cache/**', '**/package-lock.json', '**/npm-shrinkwrap.json'],
|
|
79
|
+
});
|
|
80
|
+
for await (const path of paths) {
|
|
81
|
+
const absolute = join(root, path);
|
|
82
|
+
const source = await readFile(absolute, 'utf8');
|
|
83
|
+
const result = await format(path, source, formatOptions);
|
|
84
|
+
if (result.errors.length > 0) throw new Error(JSON.stringify(result.errors));
|
|
85
|
+
if (result.code === source) continue;
|
|
86
|
+
if (check) {
|
|
87
|
+
process.stderr.write(path + '\\n');
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
} else {
|
|
90
|
+
await writeFile(absolute, result.code);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function aotPlugin() {
|
|
96
|
+
const plugin = await zmdbAot({ cwd: root });
|
|
97
|
+
return {
|
|
98
|
+
name: plugin.name,
|
|
99
|
+
setup(esbuild) {
|
|
100
|
+
esbuild.onLoad({ filter: /\\.[cm]?tsx?$/ }, async ({ path }) => {
|
|
101
|
+
const code = await readFile(path, 'utf8');
|
|
102
|
+
const result = plugin.transform(code, path);
|
|
103
|
+
return { contents: result?.code ?? code, loader: path.endsWith('x') ? 'tsx' : 'ts' };
|
|
104
|
+
});
|
|
105
|
+
esbuild.onEnd(() => plugin.buildEnd?.());
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const common = {
|
|
111
|
+
bundle: true,
|
|
112
|
+
format: 'esm',
|
|
113
|
+
packages: 'external',
|
|
114
|
+
platform: 'node',
|
|
115
|
+
target: 'es2022',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const mode = process.argv[2];
|
|
119
|
+
if (mode === 'fmt' || mode === 'fmt-check') {
|
|
120
|
+
await formatProject(mode === 'fmt-check');
|
|
121
|
+
} else if (mode === 'app') {
|
|
122
|
+
await build({
|
|
123
|
+
...common,
|
|
124
|
+
entryPoints: [join(root, 'src', 'main.ts')],
|
|
125
|
+
outfile: join(root, 'dist', 'main.mjs'),
|
|
126
|
+
plugins: [await aotPlugin()],
|
|
127
|
+
});
|
|
128
|
+
} else if (mode === 'test') {
|
|
129
|
+
const entryPoints = [];
|
|
130
|
+
for await (const path of glob('src/**/*.spec.ts', { cwd: root })) {
|
|
131
|
+
entryPoints.push(join(root, path));
|
|
132
|
+
}
|
|
133
|
+
const outdir = join(root, 'generated-tests');
|
|
134
|
+
await rm(outdir, { recursive: true, force: true });
|
|
135
|
+
await build({
|
|
136
|
+
...common,
|
|
137
|
+
entryPoints,
|
|
138
|
+
outbase: join(root, 'src'),
|
|
139
|
+
outdir,
|
|
140
|
+
outExtension: { '.js': '.mjs' },
|
|
141
|
+
plugins: [await aotPlugin()],
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error('usage: node scripts/build.mjs <app|test|fmt|fmt-check>');
|
|
145
|
+
}
|
|
146
|
+
`,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: 'vitest.config.ts',
|
|
150
|
+
source: `import { defineConfig } from 'vitest/config';
|
|
151
|
+
|
|
152
|
+
export default defineConfig({
|
|
153
|
+
test: {
|
|
154
|
+
include: ['generated-tests/**/*.spec.mjs'],
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
`,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
path: 'zmdb.config.ts',
|
|
161
|
+
source: `import { DatabaseSync } from 'node:sqlite';
|
|
162
|
+
import { fileURLToPath } from 'node:url';
|
|
163
|
+
|
|
164
|
+
import { defineConfig } from 'zmdb';
|
|
165
|
+
import { sqlite, sqliteDriver } from 'zmdb/sqlite';
|
|
166
|
+
|
|
167
|
+
const databasePath = fileURLToPath(new URL('./database.sqlite', import.meta.url));
|
|
168
|
+
|
|
169
|
+
export default defineConfig({
|
|
170
|
+
schema: ['src/**/*.ts'],
|
|
171
|
+
dialect: sqlite,
|
|
172
|
+
project: './tsconfig.json',
|
|
173
|
+
out: './migrations',
|
|
174
|
+
driver: () => sqliteDriver(new DatabaseSync(databasePath)),
|
|
175
|
+
});
|
|
176
|
+
`,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
path: 'src/app.module.ts',
|
|
180
|
+
source: `import { Module } from 'zmdb';
|
|
181
|
+
|
|
182
|
+
import { HealthController } from './health.controller.js';
|
|
183
|
+
|
|
184
|
+
@Module({ controllers: [HealthController] })
|
|
185
|
+
export class AppModule {}
|
|
186
|
+
`,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
path: 'src/main.ts',
|
|
190
|
+
source: `import { createServer } from 'node:http';
|
|
191
|
+
|
|
192
|
+
import { compileModule, createRouter, toNodeHandler } from 'zmdb/web';
|
|
193
|
+
|
|
194
|
+
import { AppModule } from './app.module.js';
|
|
195
|
+
|
|
196
|
+
const port = Number(process.env.PORT ?? '3000');
|
|
197
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
198
|
+
throw new RangeError(\`PORT must be an integer from 0 through 65535, received \${String(process.env.PORT)}\`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const compiled = compileModule(AppModule);
|
|
202
|
+
const router = createRouter();
|
|
203
|
+
for (const controller of compiled.controllers) {
|
|
204
|
+
router.register(controller);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const handle = toNodeHandler(router);
|
|
208
|
+
const server = createServer((request, response) => {
|
|
209
|
+
const method = request.method;
|
|
210
|
+
if (method === undefined) {
|
|
211
|
+
response.writeHead(400).end('request method is required');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
handle(
|
|
215
|
+
{
|
|
216
|
+
method,
|
|
217
|
+
headers: request.headers,
|
|
218
|
+
...(request.url === undefined ? {} : { url: request.url }),
|
|
219
|
+
on(event, listener) {
|
|
220
|
+
request.on(event, listener);
|
|
221
|
+
},
|
|
222
|
+
setEncoding() {
|
|
223
|
+
request.setEncoding('utf8');
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
response,
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
server.listen(port, '127.0.0.1', () => {
|
|
230
|
+
const address = server.address();
|
|
231
|
+
const selectedPort = typeof address === 'object' && address !== null ? address.port : port;
|
|
232
|
+
console.log(\`listening on http://127.0.0.1:\${String(selectedPort)}\`);
|
|
233
|
+
});
|
|
234
|
+
`,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
path: 'src/health.controller.ts',
|
|
238
|
+
source: `import { Controller, Get } from 'zmdb';
|
|
239
|
+
|
|
240
|
+
@Controller('/health')
|
|
241
|
+
export class HealthController {
|
|
242
|
+
@Get()
|
|
243
|
+
check(): { readonly ok: true } {
|
|
244
|
+
return { ok: true };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
`,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
path: 'src/health.controller.spec.ts',
|
|
251
|
+
source: `import { createTestApp } from 'zmdb/testing';
|
|
252
|
+
import { bodyText } from 'zmdb/web';
|
|
253
|
+
import { describe, expect, it } from 'vitest';
|
|
254
|
+
|
|
255
|
+
import config from '../zmdb.config.js';
|
|
256
|
+
import { AppModule } from './app.module.js';
|
|
257
|
+
|
|
258
|
+
describe('generated project', () => {
|
|
259
|
+
it('serves health and opens its sqlite database', async () => {
|
|
260
|
+
await using app = createTestApp(AppModule);
|
|
261
|
+
const response = await app.request({ method: 'GET', path: '/health', headers: {} });
|
|
262
|
+
expect(response.status).toBe(200);
|
|
263
|
+
expect(JSON.parse(await bodyText(response))).toEqual({ ok: true });
|
|
264
|
+
|
|
265
|
+
const rows = await config.driver().execute({ text: 'SELECT 1 AS ok', parameters: [] });
|
|
266
|
+
expect(Reflect.get(rows[0] ?? {}, 'ok')).toBe(1);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
`,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
path: '.gitignore',
|
|
273
|
+
source: `node_modules
|
|
274
|
+
dist
|
|
275
|
+
generated-tests
|
|
276
|
+
coverage
|
|
277
|
+
database.sqlite
|
|
278
|
+
`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const repositoryTemplate: TemplateFactory = ({ name }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: `src/${name.fileStem}.repository.ts`,
|
|
7
|
+
source: `import { defineRepository, schemaOf, type Driver } from 'zmdb';
|
|
8
|
+
import type { BaseRepository } from 'zmdb/orm';
|
|
9
|
+
import { repositoryToken } from 'zmdb/app/data';
|
|
10
|
+
import type { ProviderDef } from 'zmdb/app/modules';
|
|
11
|
+
|
|
12
|
+
import type { ${name.pascal} } from './${name.fileStem}.js';
|
|
13
|
+
|
|
14
|
+
const ${name.camel}Schema = schemaOf<${name.pascal}>();
|
|
15
|
+
|
|
16
|
+
export const ${name.constant}_REPOSITORY = repositoryToken<${name.pascal}>('${name.pascal}Repository');
|
|
17
|
+
|
|
18
|
+
export function ${name.camel}RepositoryProvider(driver: Driver): ProviderDef<BaseRepository<${name.pascal}>> {
|
|
19
|
+
return {
|
|
20
|
+
token: ${name.constant}_REPOSITORY,
|
|
21
|
+
useFactory: () => defineRepository(${name.camel}Schema, driver),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: `src/${name.fileStem}.repository.spec.ts`,
|
|
28
|
+
source: `import { DatabaseSync } from 'node:sqlite';
|
|
29
|
+
|
|
30
|
+
import { sqliteDriver } from 'zmdb/sqlite';
|
|
31
|
+
import { createTestApp } from 'zmdb/testing';
|
|
32
|
+
import { Module } from 'zmdb';
|
|
33
|
+
import { describe, expect, it } from 'vitest';
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
${name.constant}_REPOSITORY,
|
|
37
|
+
${name.camel}RepositoryProvider,
|
|
38
|
+
} from './${name.fileStem}.repository.js';
|
|
39
|
+
|
|
40
|
+
describe('${name.pascal} repository provider', () => {
|
|
41
|
+
it('creates and reads a row through sqlite', async () => {
|
|
42
|
+
const database = new DatabaseSync(':memory:');
|
|
43
|
+
try {
|
|
44
|
+
database.exec(
|
|
45
|
+
'CREATE TABLE "${name.table}" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL)',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
@Module({ providers: [${name.camel}RepositoryProvider(sqliteDriver(database))] })
|
|
49
|
+
class ${name.pascal}RepositoryTestModule {}
|
|
50
|
+
|
|
51
|
+
await using app = createTestApp(${name.pascal}RepositoryTestModule);
|
|
52
|
+
const repository = app.get(${name.constant}_REPOSITORY);
|
|
53
|
+
const created = await repository.create({ name: 'Ada' });
|
|
54
|
+
expect(created.name).toBe('Ada');
|
|
55
|
+
expect((await repository.findById(created.id))?.name).toBe('Ada');
|
|
56
|
+
} finally {
|
|
57
|
+
database.close();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
instructions: [
|
|
65
|
+
`add to src/app.module.ts, in @Module({ providers: [ … ] }):\n ${name.camel}RepositoryProvider(driver),`,
|
|
66
|
+
],
|
|
67
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { TemplateFactory } from './types.js';
|
|
2
|
+
|
|
3
|
+
export const schemaTemplate: TemplateFactory = ({ name }) => ({
|
|
4
|
+
files: [
|
|
5
|
+
{
|
|
6
|
+
path: `src/${name.fileStem}.ts`,
|
|
7
|
+
source: `import type { PrimaryKey, Serial, Sql, Table } from 'zmdb';
|
|
8
|
+
|
|
9
|
+
export interface ${name.pascal} extends Table<'${name.table}'> {
|
|
10
|
+
id: number & Sql<'integer'> & Serial & PrimaryKey;
|
|
11
|
+
name: string & Sql<'text'>;
|
|
12
|
+
}
|
|
13
|
+
`,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
path: `src/${name.fileStem}.spec.ts`,
|
|
17
|
+
source: `import { Module, is, schemaOf } from 'zmdb';
|
|
18
|
+
import { createTestApp } from 'zmdb/testing';
|
|
19
|
+
import { describe, expect, it } from 'vitest';
|
|
20
|
+
|
|
21
|
+
import type { ${name.pascal} } from './${name.fileStem}.js';
|
|
22
|
+
|
|
23
|
+
@Module({})
|
|
24
|
+
class ${name.pascal}SchemaTestModule {}
|
|
25
|
+
|
|
26
|
+
describe('${name.pascal} schema', () => {
|
|
27
|
+
it('emits the table declaration and validates through the transformer', async () => {
|
|
28
|
+
await using app = createTestApp(${name.pascal}SchemaTestModule);
|
|
29
|
+
await app.init();
|
|
30
|
+
expect(schemaOf<${name.pascal}>().table).toBe('${name.table}');
|
|
31
|
+
expect(is<{ id: number }>({ id: 'x' })).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
`,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FormatConfig } from 'oxfmt';
|
|
2
|
+
|
|
3
|
+
export const FORMAT_OPTIONS: FormatConfig = {
|
|
4
|
+
arrowParens: 'avoid',
|
|
5
|
+
bracketSpacing: true,
|
|
6
|
+
endOfLine: 'lf',
|
|
7
|
+
insertFinalNewline: true,
|
|
8
|
+
objectWrap: 'preserve',
|
|
9
|
+
printWidth: 120,
|
|
10
|
+
quoteProps: 'as-needed',
|
|
11
|
+
semi: true,
|
|
12
|
+
singleQuote: true,
|
|
13
|
+
sortImports: true,
|
|
14
|
+
sortPackageJson: true,
|
|
15
|
+
tabWidth: 2,
|
|
16
|
+
trailingComma: 'all',
|
|
17
|
+
useTabs: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ScaffoldKind = 'command' | 'controller' | 'module' | 'project' | 'repository' | 'schema';
|
|
21
|
+
|
|
22
|
+
export interface ScaffoldName {
|
|
23
|
+
readonly input: string;
|
|
24
|
+
readonly fileStem: string;
|
|
25
|
+
readonly pascal: string;
|
|
26
|
+
readonly camel: string;
|
|
27
|
+
readonly constant: string;
|
|
28
|
+
readonly table: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TemplateContext {
|
|
32
|
+
readonly name: ScaffoldName;
|
|
33
|
+
readonly packageVersion: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TemplateFile {
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly source: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TemplatePlan {
|
|
42
|
+
readonly files: readonly TemplateFile[];
|
|
43
|
+
readonly instructions?: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type TemplateFactory = (context: TemplateContext) => TemplatePlan;
|