@amerilux/netsuite-api 0.1.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/LICENSE +21 -0
- package/README.md +209 -0
- package/dist/client/apiClient.d.ts +57 -0
- package/dist/client/apiClient.js +96 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +6 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +9 -0
- package/dist/server/apiError.d.ts +9 -0
- package/dist/server/apiError.js +18 -0
- package/dist/server/defineRestlet.d.ts +15 -0
- package/dist/server/defineRestlet.js +4 -0
- package/dist/server/defineSuitelet.d.ts +16 -0
- package/dist/server/defineSuitelet.js +20 -0
- package/dist/server/endpoint.d.ts +59 -0
- package/dist/server/endpoint.js +95 -0
- package/dist/server/fileCabinet.d.ts +14 -0
- package/dist/server/fileCabinet.js +40 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.js +14 -0
- package/dist/server/rawResponse.d.ts +39 -0
- package/dist/server/rawResponse.js +28 -0
- package/dist/server/suiteletClient.d.ts +23 -0
- package/dist/server/suiteletClient.js +54 -0
- package/dist/testing/N/error.d.ts +2 -0
- package/dist/testing/N/error.js +6 -0
- package/dist/testing/N/file.d.ts +9 -0
- package/dist/testing/N/file.js +6 -0
- package/dist/testing/N/format.d.ts +8 -0
- package/dist/testing/N/format.js +4 -0
- package/dist/testing/N/https.d.ts +15 -0
- package/dist/testing/N/https.js +10 -0
- package/dist/testing/N/log.d.ts +5 -0
- package/dist/testing/N/log.js +5 -0
- package/dist/testing/N/query.d.ts +7 -0
- package/dist/testing/N/query.js +7 -0
- package/dist/testing/N/record.d.ts +14 -0
- package/dist/testing/N/record.js +11 -0
- package/dist/testing/N/runtime.d.ts +11 -0
- package/dist/testing/N/runtime.js +8 -0
- package/dist/testing/N/search.d.ts +10 -0
- package/dist/testing/N/search.js +8 -0
- package/dist/testing/N/task.d.ts +7 -0
- package/dist/testing/N/task.js +4 -0
- package/dist/testing/N/ui/serverWidget.d.ts +7 -0
- package/dist/testing/N/ui/serverWidget.js +7 -0
- package/dist/testing/N/url.d.ts +9 -0
- package/dist/testing/N/url.js +6 -0
- package/dist/testing/index.d.ts +28 -0
- package/dist/testing/index.js +32 -0
- package/dist-tooling/appReader.d.ts +12 -0
- package/dist-tooling/appReader.js +31 -0
- package/dist-tooling/cli/arguments.d.ts +9 -0
- package/dist-tooling/cli/arguments.js +34 -0
- package/dist-tooling/cli/bin.d.ts +2 -0
- package/dist-tooling/cli/bin.js +7 -0
- package/dist-tooling/cli/main.d.ts +13 -0
- package/dist-tooling/cli/main.js +88 -0
- package/dist-tooling/config.d.ts +43 -0
- package/dist-tooling/config.js +77 -0
- package/dist-tooling/controllerReader.d.ts +68 -0
- package/dist-tooling/controllerReader.js +267 -0
- package/dist-tooling/emit.d.ts +29 -0
- package/dist-tooling/emit.js +79 -0
- package/dist-tooling/file-system.d.ts +15 -0
- package/dist-tooling/file-system.js +53 -0
- package/dist-tooling/generate.d.ts +36 -0
- package/dist-tooling/generate.js +128 -0
- package/dist-tooling/index.d.ts +15 -0
- package/dist-tooling/index.js +8 -0
- package/dist-tooling/scriptsReader.d.ts +17 -0
- package/dist-tooling/scriptsReader.js +57 -0
- package/package.json +77 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ControllerProblem } from './controllerReader.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the project's app file (netsuite.ts): the application's names and the ids no controller or
|
|
4
|
+
* model owns. The client module carries it verbatim, so the file is plain declarations: exported
|
|
5
|
+
* constants and types, no imports, nothing that runs. The generator names anything else.
|
|
6
|
+
*/
|
|
7
|
+
export interface AppDeclarations {
|
|
8
|
+
/** Every exported declaration as written, JSDoc included, in file order. */
|
|
9
|
+
declarations: string[];
|
|
10
|
+
problems: ControllerProblem[];
|
|
11
|
+
}
|
|
12
|
+
export declare function readAppDeclarations(filePath: string, source: string): AppDeclarations;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { readLeadingJsDoc } from './controllerReader.js';
|
|
3
|
+
function hasExportModifier(node) {
|
|
4
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
5
|
+
}
|
|
6
|
+
function describeStatement(statement, sourceFile) {
|
|
7
|
+
return statement.getText(sourceFile).split('\n')[0].trim();
|
|
8
|
+
}
|
|
9
|
+
export function readAppDeclarations(filePath, source) {
|
|
10
|
+
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
11
|
+
const declarations = [];
|
|
12
|
+
const problems = [];
|
|
13
|
+
for (const statement of sourceFile.statements) {
|
|
14
|
+
if (ts.isImportDeclaration(statement)) {
|
|
15
|
+
problems.push({ filePath, message: `imports nothing: it is copied into the client module verbatim (found ${describeStatement(statement, sourceFile)}).` });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const isDeclaration = ts.isVariableStatement(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement);
|
|
19
|
+
if (!isDeclaration || !hasExportModifier(statement)) {
|
|
20
|
+
problems.push({ filePath, message: `holds exported constants and types only, because the client module carries it verbatim (found ${describeStatement(statement, sourceFile)}).` });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (ts.isVariableStatement(statement) && !(statement.declarationList.flags & ts.NodeFlags.Const)) {
|
|
24
|
+
problems.push({ filePath, message: `exports constants only (found ${describeStatement(statement, sourceFile)}).` });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const jsDoc = readLeadingJsDoc(statement, sourceFile);
|
|
28
|
+
declarations.push(`${jsDoc ? `${jsDoc}\n` : ''}${statement.getText(sourceFile)}`);
|
|
29
|
+
}
|
|
30
|
+
return { declarations, problems };
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedCommandLine {
|
|
2
|
+
command?: string;
|
|
3
|
+
options: Record<string, string | boolean>;
|
|
4
|
+
positional: string[];
|
|
5
|
+
}
|
|
6
|
+
/** Parses `command --key value --key=value --flag positional` without any dependency. */
|
|
7
|
+
export declare function parseCommandLineArguments(argv: string[]): ParsedCommandLine;
|
|
8
|
+
export declare function readStringOption(options: Record<string, string | boolean>, name: string): string | undefined;
|
|
9
|
+
export declare function readFlagOption(options: Record<string, string | boolean>, name: string): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Parses `command --key value --key=value --flag positional` without any dependency. */
|
|
2
|
+
export function parseCommandLineArguments(argv) {
|
|
3
|
+
const parsed = { options: {}, positional: [] };
|
|
4
|
+
for (let index = 0; index < argv.length; index++) {
|
|
5
|
+
const argument = argv[index];
|
|
6
|
+
if (argument.startsWith('--')) {
|
|
7
|
+
const body = argument.slice(2);
|
|
8
|
+
const equalsIndex = body.indexOf('=');
|
|
9
|
+
if (equalsIndex >= 0) {
|
|
10
|
+
parsed.options[body.slice(0, equalsIndex)] = body.slice(equalsIndex + 1);
|
|
11
|
+
}
|
|
12
|
+
else if (index + 1 < argv.length && !argv[index + 1].startsWith('--')) {
|
|
13
|
+
parsed.options[body] = argv[++index];
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
parsed.options[body] = true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else if (parsed.command === undefined) {
|
|
20
|
+
parsed.command = argument;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
parsed.positional.push(argument);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
export function readStringOption(options, name) {
|
|
29
|
+
const value = options[name];
|
|
30
|
+
return typeof value === 'string' ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
export function readFlagOption(options, name) {
|
|
33
|
+
return options[name] === true || options[name] === 'true';
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FileSystemAdapter } from '../file-system.js';
|
|
2
|
+
export interface CliEnvironment {
|
|
3
|
+
cwd: string;
|
|
4
|
+
fileSystem?: FileSystemAdapter;
|
|
5
|
+
stdout: (message: string) => void;
|
|
6
|
+
stderr: (message: string) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const CLI_USAGE: string;
|
|
9
|
+
export declare const EXIT_SUCCESS = 0;
|
|
10
|
+
export declare const EXIT_PROBLEMS = 1;
|
|
11
|
+
export declare const EXIT_USAGE = 2;
|
|
12
|
+
/** Runs the CLI and resolves to the process exit code. Never throws for user errors. */
|
|
13
|
+
export declare function runCli(argv: string[], environment: CliEnvironment): number;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as nodePath from 'node:path';
|
|
2
|
+
import { ClientGeneratorConfigError, loadClientGeneratorConfig } from '../config.js';
|
|
3
|
+
import { createNodeFileSystemAdapter, toPosixPath } from '../file-system.js';
|
|
4
|
+
import { checkClientGeneration, planClientGeneration, runClientGeneration } from '../generate.js';
|
|
5
|
+
import { parseCommandLineArguments, readFlagOption, readStringOption } from './arguments.js';
|
|
6
|
+
export const CLI_USAGE = [
|
|
7
|
+
'Usage: netsuite-api <command> [options]',
|
|
8
|
+
'',
|
|
9
|
+
'Commands:',
|
|
10
|
+
' generate Read the controllers and the app file; write the client module, the scripts map and the copied type files.',
|
|
11
|
+
' check Exit non-zero when a generated file is missing or out of date.',
|
|
12
|
+
' help Show this message.',
|
|
13
|
+
'',
|
|
14
|
+
'Options:',
|
|
15
|
+
' --config <path> Config file (default: netsuite-api.config.json in the working directory).',
|
|
16
|
+
' --dry-run With generate: print the client module that would be written without writing anything.',
|
|
17
|
+
].join('\n');
|
|
18
|
+
export const EXIT_SUCCESS = 0;
|
|
19
|
+
export const EXIT_PROBLEMS = 1;
|
|
20
|
+
export const EXIT_USAGE = 2;
|
|
21
|
+
const commands = ['generate', 'check'];
|
|
22
|
+
function formatProblems(plan) {
|
|
23
|
+
return plan.problems.map((problem) => ` - ${problem.filePath}: ${problem.message}`);
|
|
24
|
+
}
|
|
25
|
+
function relativeTo(cwd, filePath) {
|
|
26
|
+
return toPosixPath(nodePath.relative(cwd, filePath)) || filePath;
|
|
27
|
+
}
|
|
28
|
+
function describeControllers(plan) {
|
|
29
|
+
return plan.controllers.map((controller) => ` - ${controller.name} (${controller.kind}): ${controller.endpointCount} endpoint(s)${controller.browser ? '' : ', types only'}`);
|
|
30
|
+
}
|
|
31
|
+
/** Runs the CLI and resolves to the process exit code. Never throws for user errors. */
|
|
32
|
+
export function runCli(argv, environment) {
|
|
33
|
+
const parsed = parseCommandLineArguments(argv);
|
|
34
|
+
const fileSystem = environment.fileSystem ?? createNodeFileSystemAdapter();
|
|
35
|
+
const command = parsed.command ?? 'help';
|
|
36
|
+
if (command === 'help' || readFlagOption(parsed.options, 'help')) {
|
|
37
|
+
environment.stdout(CLI_USAGE);
|
|
38
|
+
return EXIT_SUCCESS;
|
|
39
|
+
}
|
|
40
|
+
if (!commands.includes(command)) {
|
|
41
|
+
environment.stderr(`Unknown command '${command}'.\n\n${CLI_USAGE}`);
|
|
42
|
+
return EXIT_USAGE;
|
|
43
|
+
}
|
|
44
|
+
let config;
|
|
45
|
+
try {
|
|
46
|
+
config = loadClientGeneratorConfig(fileSystem, environment.cwd, readStringOption(parsed.options, 'config'));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
environment.stderr(error instanceof ClientGeneratorConfigError ? error.message : `Could not load the config: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
return EXIT_USAGE;
|
|
51
|
+
}
|
|
52
|
+
const options = { config, fileSystem };
|
|
53
|
+
const failure = (plan) => {
|
|
54
|
+
environment.stderr(['The controllers cannot be turned into a client module:', ...formatProblems(plan)].join('\n'));
|
|
55
|
+
return EXIT_PROBLEMS;
|
|
56
|
+
};
|
|
57
|
+
if (command === 'generate') {
|
|
58
|
+
if (readFlagOption(parsed.options, 'dry-run')) {
|
|
59
|
+
const plan = planClientGeneration(options);
|
|
60
|
+
if (plan.problems.length > 0)
|
|
61
|
+
return failure(plan);
|
|
62
|
+
environment.stdout(plan.files[0].content);
|
|
63
|
+
return EXIT_SUCCESS;
|
|
64
|
+
}
|
|
65
|
+
const result = runClientGeneration(options);
|
|
66
|
+
if (result.problems.length > 0)
|
|
67
|
+
return failure(result);
|
|
68
|
+
environment.stdout([
|
|
69
|
+
`netsuite-api: ${result.controllers.length} controller(s), ${result.writtenFiles.length} file(s) written, ${result.unchangedFiles.length} unchanged.`,
|
|
70
|
+
...describeControllers(result),
|
|
71
|
+
...result.writtenFiles.map((filePath) => ` - wrote ${relativeTo(environment.cwd, filePath)}`),
|
|
72
|
+
].join('\n'));
|
|
73
|
+
return EXIT_SUCCESS;
|
|
74
|
+
}
|
|
75
|
+
const check = checkClientGeneration(options);
|
|
76
|
+
if (check.problems.length > 0)
|
|
77
|
+
return failure(check);
|
|
78
|
+
if (check.missingFiles.length > 0 || check.staleFiles.length > 0) {
|
|
79
|
+
environment.stderr([
|
|
80
|
+
'The generated files are not up to date. Run `netsuite-api generate`.',
|
|
81
|
+
...check.missingFiles.map((filePath) => ` - missing: ${relativeTo(environment.cwd, filePath)}`),
|
|
82
|
+
...check.staleFiles.map((filePath) => ` - out of date: ${relativeTo(environment.cwd, filePath)}`),
|
|
83
|
+
].join('\n'));
|
|
84
|
+
return EXIT_PROBLEMS;
|
|
85
|
+
}
|
|
86
|
+
environment.stdout([`Generated files are up to date (${check.files.length} file(s)).`, ...describeControllers(check)].join('\n'));
|
|
87
|
+
return EXIT_SUCCESS;
|
|
88
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FileSystemAdapter } from './file-system.js';
|
|
2
|
+
/**
|
|
3
|
+
* Settings of `netsuite-api generate`, read from netsuite-api.config.json at the project root. Every
|
|
4
|
+
* path is relative to the config file; the defaults match the layout create-netsuite-project scaffolds.
|
|
5
|
+
*/
|
|
6
|
+
export interface ClientGeneratorConfig {
|
|
7
|
+
/** Directory holding the controllers: one `<name>Controller.ts` per script. */
|
|
8
|
+
controllers: string;
|
|
9
|
+
/** The file declaring `app` (and any other id no controller or model owns), copied into the client module verbatim. */
|
|
10
|
+
appFile: string;
|
|
11
|
+
/** The generated client module: every wire shape, every endpoint type, one client per browser-facing controller. */
|
|
12
|
+
outFile: string;
|
|
13
|
+
/** The generated copy of the app file for the client, outside the api folder so pages and components may import it. */
|
|
14
|
+
appOutFile: string;
|
|
15
|
+
/** The generated server-side `scripts` map: what a repository passes to createSuiteletClient. */
|
|
16
|
+
scriptsOutFile: string;
|
|
17
|
+
/** The specifier the client module imports `createApiClient` from. */
|
|
18
|
+
clientModule: string;
|
|
19
|
+
/** The specifier the scripts map imports `ScriptRef` from. */
|
|
20
|
+
wireModule: string;
|
|
21
|
+
/**
|
|
22
|
+
* Type imports a controller may carry into the client module, as the specifier written in the
|
|
23
|
+
* controller mapped to the specifier the client resolves: the generated entity types, and the
|
|
24
|
+
* package's server entry mapped to its client entry (for `RawResponse`). A type imported from any
|
|
25
|
+
* other module is an error, because the client could not resolve it.
|
|
26
|
+
*/
|
|
27
|
+
typeImports: Record<string, string>;
|
|
28
|
+
/** Files copied into the client as they are, source to destination: the generated entity types the carried imports point at. */
|
|
29
|
+
copyFiles: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
export interface ResolvedClientGeneratorConfig extends ClientGeneratorConfig {
|
|
32
|
+
/** The config file's directory, or the working directory when no config file exists and the defaults apply. */
|
|
33
|
+
rootDirectory: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const DEFAULT_CONFIG_FILE_NAME = "netsuite-api.config.json";
|
|
36
|
+
export declare const defaultClientGeneratorConfig: ClientGeneratorConfig;
|
|
37
|
+
export declare class ClientGeneratorConfigError extends Error {
|
|
38
|
+
readonly configPath: string;
|
|
39
|
+
readonly problems: string[];
|
|
40
|
+
constructor(configPath: string, problems: string[]);
|
|
41
|
+
}
|
|
42
|
+
/** Loads the config in the working directory (or the one named), applying the defaults for what it omits. */
|
|
43
|
+
export declare function loadClientGeneratorConfig(fileSystem: FileSystemAdapter, cwd: string, configPath?: string): ResolvedClientGeneratorConfig;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as nodePath from 'node:path';
|
|
2
|
+
export const DEFAULT_CONFIG_FILE_NAME = 'netsuite-api.config.json';
|
|
3
|
+
export const defaultClientGeneratorConfig = {
|
|
4
|
+
controllers: 'api/src/controllers',
|
|
5
|
+
appFile: 'netsuite.ts',
|
|
6
|
+
outFile: 'client/src/api/index.gen.ts',
|
|
7
|
+
appOutFile: 'client/src/app.gen.ts',
|
|
8
|
+
scriptsOutFile: 'api/src/scripts.gen.ts',
|
|
9
|
+
clientModule: '@amerilux/netsuite-api/client',
|
|
10
|
+
wireModule: '@amerilux/netsuite-api',
|
|
11
|
+
typeImports: { '../types/models.gen': './models.gen', '@amerilux/netsuite-api/server': '@amerilux/netsuite-api/client' },
|
|
12
|
+
copyFiles: { 'api/src/types/models.gen.ts': 'client/src/api/models.gen.ts' },
|
|
13
|
+
};
|
|
14
|
+
export class ClientGeneratorConfigError extends Error {
|
|
15
|
+
configPath;
|
|
16
|
+
problems;
|
|
17
|
+
constructor(configPath, problems) {
|
|
18
|
+
super(`Config '${configPath}' is invalid:\n - ${problems.join('\n - ')}`);
|
|
19
|
+
this.configPath = configPath;
|
|
20
|
+
this.problems = problems;
|
|
21
|
+
this.name = 'ClientGeneratorConfigError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const stringSettings = ['controllers', 'appFile', 'outFile', 'appOutFile', 'scriptsOutFile', 'clientModule', 'wireModule'];
|
|
25
|
+
const mapSettings = ['typeImports', 'copyFiles'];
|
|
26
|
+
function isStringMap(value) {
|
|
27
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === 'string' && entry !== '');
|
|
28
|
+
}
|
|
29
|
+
function validateClientGeneratorConfig(raw, configPath) {
|
|
30
|
+
const problems = [];
|
|
31
|
+
const config = { ...defaultClientGeneratorConfig, typeImports: { ...defaultClientGeneratorConfig.typeImports }, copyFiles: { ...defaultClientGeneratorConfig.copyFiles } };
|
|
32
|
+
for (const setting of stringSettings) {
|
|
33
|
+
const value = raw[setting];
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
37
|
+
config[setting] = value;
|
|
38
|
+
else
|
|
39
|
+
problems.push(`'${setting}' must be a non-empty string.`);
|
|
40
|
+
}
|
|
41
|
+
for (const setting of mapSettings) {
|
|
42
|
+
const value = raw[setting];
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (isStringMap(value))
|
|
46
|
+
config[setting] = value;
|
|
47
|
+
else
|
|
48
|
+
problems.push(`'${setting}' must be an object of strings.`);
|
|
49
|
+
}
|
|
50
|
+
const known = new Set([...stringSettings, ...mapSettings]);
|
|
51
|
+
for (const key of Object.keys(raw)) {
|
|
52
|
+
if (!known.has(key))
|
|
53
|
+
problems.push(`'${key}' is not a setting.`);
|
|
54
|
+
}
|
|
55
|
+
if (problems.length > 0)
|
|
56
|
+
throw new ClientGeneratorConfigError(configPath, problems);
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
59
|
+
/** Loads the config in the working directory (or the one named), applying the defaults for what it omits. */
|
|
60
|
+
export function loadClientGeneratorConfig(fileSystem, cwd, configPath) {
|
|
61
|
+
const resolvedConfigPath = nodePath.resolve(cwd, configPath ?? DEFAULT_CONFIG_FILE_NAME);
|
|
62
|
+
if (!fileSystem.fileExists(resolvedConfigPath)) {
|
|
63
|
+
if (configPath !== undefined)
|
|
64
|
+
throw new ClientGeneratorConfigError(resolvedConfigPath, ['the file does not exist.']);
|
|
65
|
+
return { ...defaultClientGeneratorConfig, rootDirectory: cwd };
|
|
66
|
+
}
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = JSON.parse(fileSystem.readTextFile(resolvedConfigPath));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new ClientGeneratorConfigError(resolvedConfigPath, [`not valid JSON: ${error instanceof Error ? error.message : String(error)}`]);
|
|
73
|
+
}
|
|
74
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
75
|
+
throw new ClientGeneratorConfigError(resolvedConfigPath, ['must be a JSON object.']);
|
|
76
|
+
return { ...validateClientGeneratorConfig(raw, resolvedConfigPath), rootDirectory: nodePath.dirname(resolvedConfigPath) };
|
|
77
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
/**
|
|
3
|
+
* Reads what a controller puts on the wire, from its source alone: the script it declares in its
|
|
4
|
+
* defineRestlet or defineSuitelet call, the exported types (the DTOs), and the name, request type and
|
|
5
|
+
* response type of every endpoint in its `defineEndpoints({ ... })`. A parse, not a type check: the
|
|
6
|
+
* handler annotations are the contract, so they must be written out, a DTO may only reference types
|
|
7
|
+
* from the carried modules or from another controller, and the script ids are string literals.
|
|
8
|
+
*/
|
|
9
|
+
export interface ControllerProblem {
|
|
10
|
+
filePath: string;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export interface EndpointSignature {
|
|
14
|
+
name: string;
|
|
15
|
+
/** The handler's parameter type, or undefined when the handler takes no request. */
|
|
16
|
+
requestType?: string;
|
|
17
|
+
requestOptional: boolean;
|
|
18
|
+
responseType: string;
|
|
19
|
+
/** True when the return type is written `RawResponse`: the endpoint answers with a document, and the client resolves it to a Blob. */
|
|
20
|
+
raw: boolean;
|
|
21
|
+
jsDoc?: string;
|
|
22
|
+
}
|
|
23
|
+
/** The return type a handler writes, exactly, to answer with a document instead of the envelope. */
|
|
24
|
+
export declare const RAW_RESPONSE_TYPE_NAME = "RawResponse";
|
|
25
|
+
export interface CarriedTypeImport {
|
|
26
|
+
/** The specifier as the client resolves it, after the typeImports mapping. */
|
|
27
|
+
moduleSpecifier: string;
|
|
28
|
+
names: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface TypeDeclaration {
|
|
31
|
+
name: string;
|
|
32
|
+
/** The declaration as written, JSDoc included. */
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
35
|
+
export type ControllerKind = 'restlet' | 'suitelet';
|
|
36
|
+
/** The script a controller declares, as read off its entry point. */
|
|
37
|
+
export interface DeclaredScript {
|
|
38
|
+
kind: ControllerKind;
|
|
39
|
+
scriptId: string;
|
|
40
|
+
deployId: string;
|
|
41
|
+
browser: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface ControllerContract {
|
|
44
|
+
/** The controller's name: `user` for userController.ts, matching `name` in its declaration. */
|
|
45
|
+
name: string;
|
|
46
|
+
filePath: string;
|
|
47
|
+
/** `UserEndpoints`: the type the generated module declares for the controller. */
|
|
48
|
+
endpointsTypeName: string;
|
|
49
|
+
/** `userApi`: the client the generated module exports for a browser-facing controller. */
|
|
50
|
+
clientName: string;
|
|
51
|
+
script: DeclaredScript;
|
|
52
|
+
typeImports: CarriedTypeImport[];
|
|
53
|
+
typeDeclarations: TypeDeclaration[];
|
|
54
|
+
endpoints: EndpointSignature[];
|
|
55
|
+
}
|
|
56
|
+
export interface ReadControllerOptions {
|
|
57
|
+
/** Type imports a controller may carry: the specifier written in the controller mapped to the specifier the client resolves. */
|
|
58
|
+
typeImports: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
export interface ControllerReadResult {
|
|
61
|
+
contract?: ControllerContract;
|
|
62
|
+
problems: ControllerProblem[];
|
|
63
|
+
}
|
|
64
|
+
export declare function isControllerFileName(fileName: string): boolean;
|
|
65
|
+
export declare function toPascalCase(name: string): string;
|
|
66
|
+
/** The JSDoc block directly above a node (no blank line between them), or undefined. */
|
|
67
|
+
export declare function readLeadingJsDoc(node: ts.Node, sourceFile: ts.SourceFile): string | undefined;
|
|
68
|
+
export declare function readControllerContract(filePath: string, source: string, options: ReadControllerOptions): ControllerReadResult;
|