@ultimat3/cli 1.1.0 → 2.0.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/CLAUDE.md +724 -0
- package/README.md +41 -9
- package/package.json +25 -23
- package/src/api-routes.ts +16 -0
- package/src/app-auth.ts +32 -0
- package/src/app-entities.ts +18 -0
- package/src/app-env.ts +103 -0
- package/src/app-load.ts +20 -3
- package/src/bin.ts +4 -3
- package/src/budgets.ts +114 -9
- package/src/cmd-build.ts +69 -21
- package/src/cmd-db-branch.ts +215 -0
- package/src/cmd-db.ts +332 -155
- package/src/cmd-deploy.ts +59 -6
- package/src/cmd-dev.ts +87 -17
- package/src/cmd-docs.ts +167 -0
- package/src/cmd-doctor.ts +64 -9
- package/src/cmd-env.ts +95 -0
- package/src/cmd-errors.ts +33 -13
- package/src/cmd-fix.ts +5 -1
- package/src/cmd-generate.ts +146 -111
- package/src/cmd-help.ts +16 -5
- package/src/cmd-i18n.ts +2 -0
- package/src/cmd-jobs.ts +47 -33
- package/src/cmd-mcp.ts +11 -2
- package/src/cmd-new.ts +13 -7
- package/src/cmd-planned.ts +55 -10
- package/src/cmd-policy.ts +1 -0
- package/src/cmd-registries.ts +3 -0
- package/src/cmd-secrets.ts +368 -0
- package/src/cmd-tasks.ts +1 -0
- package/src/cmd-test.ts +17 -23
- package/src/cmd-verify.ts +177 -23
- package/src/db-backfill.ts +401 -0
- package/src/db-branch.ts +251 -0
- package/src/db-destructive.ts +29 -0
- package/src/db-finding.ts +28 -0
- package/src/db-generate.ts +112 -0
- package/src/db-snapshot.ts +24 -0
- package/src/dev-assets.ts +86 -20
- package/src/dev-cache.ts +122 -0
- package/src/dev-dashboard.ts +19 -4
- package/src/dev-hooks.ts +27 -2
- package/src/dev-n-plus-one.ts +191 -0
- package/src/dev-queue.ts +105 -19
- package/src/dev-render.ts +158 -26
- package/src/dev-roles-fixture.ts +67 -0
- package/src/dev-roles.ts +186 -78
- package/src/dev-runtime.ts +117 -40
- package/src/dev-services.ts +15 -0
- package/src/dev-storage.ts +245 -0
- package/src/dev-sync.ts +107 -0
- package/src/dev-traces.ts +11 -3
- package/src/dispatch.ts +4 -2
- package/src/document-styles.ts +54 -0
- package/src/drift.ts +37 -9
- package/src/error-catalog.ts +7 -18
- package/src/error-codes.ts +186 -0
- package/src/error-contract.ts +29 -7
- package/src/error-fixes.ts +114 -0
- package/src/errors.ts +205 -140
- package/src/fix-command.ts +268 -0
- package/src/flag-number.ts +56 -0
- package/src/framework-scope.ts +49 -0
- package/src/generate-kinds.ts +97 -0
- package/src/guards.ts +186 -0
- package/src/index.ts +87 -14
- package/src/island-bundle.ts +166 -0
- package/src/island-routes.ts +50 -0
- package/src/jobs-driver.ts +33 -0
- package/src/jobs-json.ts +24 -0
- package/src/jobs-report.ts +17 -4
- package/src/mcp-db-target.ts +52 -27
- package/src/mcp-errors.ts +120 -19
- package/src/mcp-host.ts +44 -25
- package/src/messages.ts +81 -2
- package/src/metrics-endpoint.ts +73 -0
- package/src/migrations.ts +37 -4
- package/src/otlp-export.ts +64 -0
- package/src/output.ts +46 -16
- package/src/parse.ts +41 -3
- package/src/policy-facts.ts +38 -6
- package/src/policy-fixture.ts +14 -7
- package/src/prerender.ts +111 -2
- package/src/registry.ts +21 -3
- package/src/runtime-overrides.ts +66 -0
- package/src/safe-url-label.ts +24 -0
- package/src/scaffold-fixture.ts +10 -0
- package/src/scaffold-typecheck.ts +16 -38
- package/src/serve.ts +202 -18
- package/src/source-files.ts +4 -0
- package/src/statement-loop.ts +74 -0
- package/src/style-csp.ts +18 -0
- package/src/sync-authenticator.ts +59 -0
- package/src/templates/action.ts +15 -30
- package/src/templates/admin-page.ts +103 -0
- package/src/templates/admin.ts +11 -7
- package/src/templates/backfill.ts +212 -0
- package/src/templates/entity.ts +72 -31
- package/src/templates/guard.ts +143 -0
- package/src/templates/index.ts +12 -1
- package/src/templates/island.ts +67 -0
- package/src/templates/job.ts +53 -13
- package/src/templates/naming.ts +17 -1
- package/src/templates/policy.ts +35 -28
- package/src/templates/query.ts +24 -5
- package/src/templates/resource.ts +19 -11
- package/src/templates/route.ts +90 -15
- package/src/templates/scaffold-app.ts +142 -45
- package/src/templates/scaffold-claude-agents.ts +149 -0
- package/src/templates/scaffold-claude-commands.ts +221 -0
- package/src/templates/scaffold-claude.ts +134 -0
- package/src/templates/scaffold-container.ts +46 -2
- package/src/templates/scaffold-db-package.ts +91 -0
- package/src/templates/scaffold-docs.ts +24 -5
- package/src/templates/scaffold-domain-package.ts +90 -0
- package/src/templates/scaffold-env.ts +87 -0
- package/src/templates/scaffold-i18n.ts +4 -1
- package/src/templates/scaffold-mcp-package.ts +49 -0
- package/src/templates/scaffold-package-shape.ts +25 -4
- package/src/templates/scaffold-repo.ts +116 -257
- package/src/templates/scaffold-roles.ts +68 -0
- package/src/templates/scaffold-ui-package.ts +56 -0
- package/src/templates/slice-foundation.ts +88 -0
- package/src/templates/wrap.ts +95 -0
- package/src/test-counts.ts +35 -0
- package/src/test-select.ts +30 -15
- package/src/test-shards.ts +21 -3
- package/src/test-workers.ts +47 -0
- package/src/ts-scan.ts +271 -13
- package/src/tsconfig-references.ts +78 -0
- package/src/verify-floor.ts +133 -0
- package/src/verify-step.ts +19 -0
- package/src/verify-test-run.ts +72 -0
- package/src/verify-tests.ts +160 -71
- package/src/version-loader.ts +20 -3
- package/src/workspace-checks.ts +87 -16
- package/src/write-line.ts +34 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// The generated app's `packages/domain`: the pure types and constants every other package reads,
|
|
2
|
+
// with no I/O of any kind. Split out of scaffold-repo.ts, which was over the file-size ceiling —
|
|
3
|
+
// one file per generated workspace package, the seam `scaffold-i18n.ts` already draws.
|
|
4
|
+
|
|
5
|
+
import type { GeneratedFile, NameSet } from './naming';
|
|
6
|
+
import { packageShapeFiles, workspacePackageJson } from './scaffold-package-shape';
|
|
7
|
+
import { wrapImport } from './wrap';
|
|
8
|
+
|
|
9
|
+
const DESCRIPTION = 'Pure types and constants, no I/O';
|
|
10
|
+
|
|
11
|
+
/** `X_MYAPP_CURRENCY_MISMATCH` — the app's own namespace, so it can never collide with a framework
|
|
12
|
+
* code and `x errors explain` can tell whose it is. */
|
|
13
|
+
const currencyCode = (app: NameSet): string =>
|
|
14
|
+
`X_${app.kebab.toUpperCase().split('-').join('_')}_CURRENCY_MISMATCH`;
|
|
15
|
+
|
|
16
|
+
const domainIndex = (
|
|
17
|
+
app: NameSet,
|
|
18
|
+
): string => `// Pure types and constants. No I/O of any kind: no fs, no network, no database, no env reads.
|
|
19
|
+
// \`Money\` is @ultimat3/schema's, re-exported and never restated: one declaration of
|
|
20
|
+
// { minor, currency } is what makes an entity column, a form field and this module the same type.
|
|
21
|
+
import { UltimateError } from '@ultimat3/core';
|
|
22
|
+
import type { MoneyValue as Money } from '@ultimat3/schema';
|
|
23
|
+
|
|
24
|
+
export type { MoneyValue as Money } from '@ultimat3/schema';
|
|
25
|
+
|
|
26
|
+
export const ROLES = ['owner', 'member', 'viewer'] as const;
|
|
27
|
+
|
|
28
|
+
export type Role = (typeof ROLES)[number];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Never a bare Error: an agent reading the failure needs the code, the cause and the exact command
|
|
32
|
+
* that resolves it. Adding across currencies is a bug in the caller's data, not a runtime hiccup,
|
|
33
|
+
* so the fix names the conversion that has to happen first.
|
|
34
|
+
*/
|
|
35
|
+
export class ${app.pascal}CurrencyMismatchError extends UltimateError {
|
|
36
|
+
constructor(input: { readonly left: string; readonly right: string }) {
|
|
37
|
+
super({
|
|
38
|
+
code: '${currencyCode(app)}',
|
|
39
|
+
cause: \`cannot add \${input.left} to \${input.right}: Money is only additive inside one currency\`,
|
|
40
|
+
fix: \`bun test packages/domain/src/index.test.ts # add() is defined only inside one currency — convert \${input.right} to \${input.left} before calling it\`,
|
|
41
|
+
docs: 'https://ultimate.dev/errors/${currencyCode(app)}',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const zero = (currency: string): Money => ({ minor: 0, currency });
|
|
47
|
+
|
|
48
|
+
export const add = (a: Money, b: Money): Money => {
|
|
49
|
+
if (a.currency !== b.currency) {
|
|
50
|
+
throw new ${app.pascal}CurrencyMismatchError({ left: a.currency, right: b.currency });
|
|
51
|
+
}
|
|
52
|
+
return { minor: a.minor + b.minor, currency: a.currency };
|
|
53
|
+
};
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
const domainTest = (
|
|
57
|
+
app: NameSet,
|
|
58
|
+
): string => `// Money arithmetic in minor units, and the currency mismatch that must throw rather than round.
|
|
59
|
+
// A float here is a rounding error nobody sees until it is on an invoice.
|
|
60
|
+
import { expect } from 'bun:test';
|
|
61
|
+
import { unitTest } from '@ultimat3/testing';
|
|
62
|
+
${wrapImport(['add', `${app.pascal}CurrencyMismatchError`, 'zero'], './index')}
|
|
63
|
+
|
|
64
|
+
unitTest('money adds in minor units', () => {
|
|
65
|
+
expect(add({ minor: 1050, currency: 'USD' }, { minor: 250, currency: 'USD' })).toEqual({
|
|
66
|
+
minor: 1300,
|
|
67
|
+
currency: 'USD',
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
unitTest('money refuses to add across currencies, with a code and a fix', () => {
|
|
72
|
+
expect(() => add(zero('USD'), zero('EUR'))).toThrow(${app.pascal}CurrencyMismatchError);
|
|
73
|
+
try {
|
|
74
|
+
add(zero('USD'), zero('EUR'));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
expect(error).toBeUltimateError('${currencyCode(app)}');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
/** Every file the `packages/domain` workspace ships, in the order `x new` writes them. */
|
|
82
|
+
export const domainPackageFiles = (app: NameSet): readonly GeneratedFile[] => [
|
|
83
|
+
{
|
|
84
|
+
path: 'packages/domain/package.json',
|
|
85
|
+
contents: workspacePackageJson(app, 'domain', DESCRIPTION),
|
|
86
|
+
},
|
|
87
|
+
...packageShapeFiles(app, 'domain', DESCRIPTION),
|
|
88
|
+
{ path: 'packages/domain/src/index.ts', contents: domainIndex(app) },
|
|
89
|
+
{ path: 'packages/domain/src/index.test.ts', contents: domainTest(app) },
|
|
90
|
+
];
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// The scaffolded app's environment, declared ONCE as a real `EnvSchema` and projected twice: into
|
|
2
|
+
// the `envSchema` export of `app.config.ts`, and into the `.env.example` committed beside it. A
|
|
3
|
+
// hand-written example file would be a second list of variable names, and `x new` would ship an
|
|
4
|
+
// app whose own `x verify` fails on the drift the framework exists to prevent.
|
|
5
|
+
|
|
6
|
+
import type { EnvSchema, EnvVarDecl } from '@ultimat3/core';
|
|
7
|
+
import { renderEnvExample } from '@ultimat3/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Committed defaults only, so a fresh clone boots with `x dev` and no scavenger hunt. Every
|
|
11
|
+
* description stays under ~80 characters: the serializer below indents them four spaces inside a
|
|
12
|
+
* template Biome then checks at 100 columns, and a reflowed line is a failing `x verify` on file
|
|
13
|
+
* one of a brand-new app.
|
|
14
|
+
*/
|
|
15
|
+
export const SCAFFOLD_ENV_SCHEMA = {
|
|
16
|
+
DATABASE_URL: {
|
|
17
|
+
type: 'url',
|
|
18
|
+
required: false,
|
|
19
|
+
description: 'Postgres URL. Empty means embedded: x dev runs PGlite in-process.',
|
|
20
|
+
},
|
|
21
|
+
PORT: {
|
|
22
|
+
type: 'port',
|
|
23
|
+
default: 3000,
|
|
24
|
+
description: 'HTTP port this role binds. Every PaaS injects it.',
|
|
25
|
+
},
|
|
26
|
+
NATS_URL: {
|
|
27
|
+
type: 'url',
|
|
28
|
+
required: false,
|
|
29
|
+
role: 'sync',
|
|
30
|
+
description: 'Realtime fan-out cluster. Only the sync role is asked for it.',
|
|
31
|
+
},
|
|
32
|
+
SESSION_SECRET: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
required: false,
|
|
35
|
+
secret: true,
|
|
36
|
+
description: 'Session cookie signing key. Generate: openssl rand -hex 32',
|
|
37
|
+
},
|
|
38
|
+
} satisfies EnvSchema;
|
|
39
|
+
|
|
40
|
+
const quoted = (value: string): string => `'${value.replaceAll("'", "\\'")}'`;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The value kinds an `EnvVarDecl` field can hold in a scaffold: a string, a number, a boolean, or
|
|
44
|
+
* the string list `enum`'s `values` and a multi-role `role` use. `RegExp` (`pattern`) is
|
|
45
|
+
* deliberately not one — its source would need re-escaping into a literal, and the scaffold has no
|
|
46
|
+
* use for it. An unrenderable value is dropped rather than guessed at, so the projection stays
|
|
47
|
+
* total and `envSchemaSource` can never emit `[object Object]` into a generated app.
|
|
48
|
+
*/
|
|
49
|
+
const literal = (value: unknown): string | undefined => {
|
|
50
|
+
if (typeof value === 'string') return quoted(value);
|
|
51
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
52
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) {
|
|
53
|
+
return `[${value.map(quoted).join(', ')}]`;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The declaration as Biome-clean TypeScript. Always multi-line — Biome, like Prettier, keeps an
|
|
60
|
+
* object expanded when the source has a newline after its `{`, so the emitted text is a fixed
|
|
61
|
+
* point of the formatter and `x new` → `x verify` never fails on its own scaffold's layout.
|
|
62
|
+
*/
|
|
63
|
+
export function envSchemaSource(schema: EnvSchema = SCAFFOLD_ENV_SCHEMA): string {
|
|
64
|
+
// Biome prints an empty object inline no matter what the source did, so the empty case is
|
|
65
|
+
// emitted the way the formatter would rewrite it — otherwise the scaffold's own `lint` step
|
|
66
|
+
// would fail on a file the scaffold wrote.
|
|
67
|
+
if (Object.keys(schema).length === 0) return 'export const envSchema = {} satisfies EnvSchema;';
|
|
68
|
+
const lines = ['export const envSchema = {'];
|
|
69
|
+
for (const [key, decl] of Object.entries(schema)) {
|
|
70
|
+
lines.push(` ${key}: {`);
|
|
71
|
+
for (const [prop, value] of Object.entries(decl)) {
|
|
72
|
+
const rendered = literal(value);
|
|
73
|
+
if (rendered !== undefined) lines.push(` ${prop}: ${rendered},`);
|
|
74
|
+
}
|
|
75
|
+
lines.push(' },');
|
|
76
|
+
}
|
|
77
|
+
lines.push('} satisfies EnvSchema;');
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Byte-identical to what `x env example` writes, because both call core's one renderer. */
|
|
82
|
+
export const envExampleSource = (schema: EnvSchema = SCAFFOLD_ENV_SCHEMA): string =>
|
|
83
|
+
renderEnvExample(schema);
|
|
84
|
+
|
|
85
|
+
/** Named so a reader of `repoFiles` can see the two projections come from one declaration. */
|
|
86
|
+
export const scaffoldEnvVarNames = (): readonly string[] =>
|
|
87
|
+
Object.keys(SCAFFOLD_ENV_SCHEMA as Readonly<Record<string, EnvVarDecl>>);
|
|
@@ -110,7 +110,10 @@ const i18nCatalog = (app: NameSet): string =>
|
|
|
110
110
|
'admin.home.description': `Operations for ${app.pascal}.`,
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
const i18nTest =
|
|
113
|
+
const i18nTest =
|
|
114
|
+
(): string => `// Every locale carries the same keys as the default one. A key present in en and missing in fr
|
|
115
|
+
// renders \u27e6key\u27e7 to a user, and nothing else in the app can notice.
|
|
116
|
+
import { expect } from 'bun:test';
|
|
114
117
|
import { unitTest } from '@ultimat3/testing';
|
|
115
118
|
import { catalogs } from './index';
|
|
116
119
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// The generated app's `packages/mcp`: the app's own MCP surface, projected straight from the
|
|
2
|
+
// action registry rather than re-listed, plus the test that pins every projected tool describing
|
|
3
|
+
// itself.
|
|
4
|
+
|
|
5
|
+
import type { GeneratedFile, NameSet } from './naming';
|
|
6
|
+
import { packageShapeFiles, workspacePackageJson } from './scaffold-package-shape';
|
|
7
|
+
|
|
8
|
+
const DESCRIPTION = "The app's own MCP tools";
|
|
9
|
+
|
|
10
|
+
const mcpIndex = (
|
|
11
|
+
app: NameSet,
|
|
12
|
+
): string => `// The app's own MCP tools. Every action with mcp.expose is already a tool; add app-specific
|
|
13
|
+
// read-only helpers here. Authorization is the action's policy, unchanged.
|
|
14
|
+
import * as api from '@${app.kebab}/web/api/health';
|
|
15
|
+
import { registerActions } from '@ultimat3/action';
|
|
16
|
+
import { defineAppMcp } from '@ultimat3/mcp';
|
|
17
|
+
|
|
18
|
+
// Names come from export names, so the registry agrees with the module the app already wrote.
|
|
19
|
+
registerActions(api);
|
|
20
|
+
|
|
21
|
+
// \`include: 'exposed'\` projects straight from the registry. Re-listing the actions here would
|
|
22
|
+
// copy \`mcp: { expose: true }\` into a second place, and the copy goes stale in silence.
|
|
23
|
+
export const mcp = defineAppMcp({
|
|
24
|
+
name: '${app.kebab}',
|
|
25
|
+
include: 'exposed',
|
|
26
|
+
});
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const mcpTest =
|
|
30
|
+
(): string => `// The app exposes its actions as MCP tools, and each tool carries the action's own policy. An
|
|
31
|
+
// agent reaching a tool that authorises differently is the failure this rules out.
|
|
32
|
+
import { expect, unitTest } from '@ultimat3/testing';
|
|
33
|
+
import { mcp } from './index';
|
|
34
|
+
|
|
35
|
+
unitTest('the app exposes its actions as MCP tools', () => {
|
|
36
|
+
expect(mcp.tools.length).toBeGreaterThan(0);
|
|
37
|
+
// Every projected tool must describe itself: an agent picks a tool by its description. Assert
|
|
38
|
+
// on the value, not its length — a failure then prints the empty description, not "0 > 0".
|
|
39
|
+
for (const tool of mcp.tools) expect(tool.description).not.toBe('');
|
|
40
|
+
});
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
/** Every file the `packages/mcp` workspace ships, in the order `x new` writes them. */
|
|
44
|
+
export const mcpPackageFiles = (app: NameSet): readonly GeneratedFile[] => [
|
|
45
|
+
{ path: 'packages/mcp/package.json', contents: workspacePackageJson(app, 'mcp', DESCRIPTION) },
|
|
46
|
+
...packageShapeFiles(app, 'mcp', DESCRIPTION),
|
|
47
|
+
{ path: 'packages/mcp/src/index.ts', contents: mcpIndex(app) },
|
|
48
|
+
{ path: 'packages/mcp/src/index.test.ts', contents: mcpTest() },
|
|
49
|
+
];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// The three contract files `x verify`'s `package-shape` step requires of every
|
|
2
|
-
// enforced on a generated app exactly like it is on this repo, so `x new` has
|
|
3
|
-
// rather than leave the app's very first gate run red.
|
|
4
|
-
//
|
|
1
|
+
// The manifest, plus the three contract files `x verify`'s `package-shape` step requires of every
|
|
2
|
+
// `packages/*` dir — enforced on a generated app exactly like it is on this repo, so `x new` has
|
|
3
|
+
// to ship them itself rather than leave the app's very first gate run red. Every scaffolded
|
|
4
|
+
// workspace package calls both exports here exactly once.
|
|
5
5
|
|
|
6
6
|
import type { GeneratedFile, NameSet } from './naming';
|
|
7
7
|
|
|
@@ -47,3 +47,24 @@ export const packageShapeFiles = (
|
|
|
47
47
|
{ path: `packages/${name}/CLAUDE.md`, contents: packageClaude(app, name, description) },
|
|
48
48
|
{ path: `packages/${name}/tsconfig.json`, contents: packageTsconfig(includes) },
|
|
49
49
|
];
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The manifest every scaffolded `packages/*` carries. Private, versionless-by-convention and
|
|
53
|
+
* dependency-free: these packages only re-export a framework package's types, so the one that does
|
|
54
|
+
* name a dependency writes its own (`scaffold-i18n.ts`, and it says why). Lives beside
|
|
55
|
+
* `packageShapeFiles` because every caller of one calls the other.
|
|
56
|
+
*/
|
|
57
|
+
export const workspacePackageJson = (app: NameSet, name: string, description: string): string => `{
|
|
58
|
+
"name": "@${app.kebab}/${name}",
|
|
59
|
+
"version": "0.0.0",
|
|
60
|
+
"private": true,
|
|
61
|
+
"type": "module",
|
|
62
|
+
"description": "${description}",
|
|
63
|
+
"exports": {
|
|
64
|
+
".": "./src/index.ts"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"typecheck": "tsc --noEmit -p ../../tsconfig.json"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`;
|