create-openibm 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/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # `create-openibm`
2
+
3
+ Scaffold a new IBM i application in seconds — interactive CLI that asks what you need and wires everything up.
4
+
5
+ ```bash
6
+ npm create openibm@latest
7
+ # or
8
+ npx create-openibm
9
+ # or pass the project name directly
10
+ npx create-openibm my-ibmi-app
11
+ ```
12
+
13
+ ---
14
+
15
+ ## What it does
16
+
17
+ ```
18
+ ◆ create-openibm
19
+
20
+ ◇ Project name
21
+ │ my-ibmi-app
22
+
23
+ ◇ Framework
24
+ │ Express
25
+
26
+ ◇ IBM i transport
27
+ │ HTTP (SSH tunnel — recommended for development)
28
+
29
+ ◇ Starter examples (space to toggle, enter to confirm)
30
+ │ ◼ Program call
31
+ │ ◼ Table query
32
+
33
+ ◇ Package manager
34
+ │ npm
35
+
36
+ ◇ Initialize a git repository?
37
+ │ Yes
38
+
39
+ ◇ Install dependencies now?
40
+ │ Yes
41
+
42
+ ◆ Done! Next steps:
43
+
44
+ cd my-ibmi-app
45
+ cp .env.example .env
46
+ # fill in IBMI_SYSTEM, IBMI_USER, IBMI_PASS
47
+ npm run generate
48
+ npm run dev
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Options
54
+
55
+ | Question | Choices |
56
+ |---|---|
57
+ | Framework | **None** (plain Node.js), **Express**, **NestJS** |
58
+ | Transport | **HTTP** (SSH tunnel), **HTTPS**, **SSH** (direct), **ODBC** (native DB2) |
59
+ | Starter examples | **Program call** (XMLSERVICE *PGM), **Table query** (DB2 query builder) |
60
+ | Package manager | **npm**, **pnpm**, **Yarn** |
61
+ | Git init | Yes / No |
62
+ | Install deps | Yes / No |
63
+
64
+ ---
65
+
66
+ ## Generated structure
67
+
68
+ ### None (plain Node.js)
69
+
70
+ ```
71
+ my-ibmi-app/
72
+ ├── schema.ibmi ← define your programs and tables here
73
+ ├── src/
74
+ │ ├── index.ts ← connect, call, query, disconnect
75
+ │ └── generated/ibmi/ ← created by: npm run generate
76
+ ├── .env.example
77
+ ├── tsconfig.json
78
+ └── package.json
79
+ ```
80
+
81
+ ### Express
82
+
83
+ ```
84
+ my-ibmi-app/
85
+ ├── schema.ibmi
86
+ ├── src/
87
+ │ ├── index.ts ← Express server with IBM i routes
88
+ │ └── generated/ibmi/
89
+ ├── .env.example
90
+ ├── tsconfig.json
91
+ └── package.json
92
+ ```
93
+
94
+ ### NestJS
95
+
96
+ ```
97
+ my-ibmi-app/
98
+ ├── schema.ibmi
99
+ ├── src/
100
+ │ ├── main.ts
101
+ │ ├── app.module.ts
102
+ │ ├── ibmi/
103
+ │ │ ├── ibmi.module.ts ← provides IBMiService
104
+ │ │ ├── ibmi.service.ts ← wraps generated client, auto connect/disconnect
105
+ │ │ └── ibmi.controller.ts ← HTTP endpoints
106
+ │ └── generated/ibmi/
107
+ ├── .env.example
108
+ ├── tsconfig.json
109
+ └── package.json
110
+ ```
111
+
112
+ ---
113
+
114
+ ## After scaffolding
115
+
116
+ ```bash
117
+ cp .env.example .env
118
+ ```
119
+
120
+ Fill in your IBM i credentials:
121
+
122
+ ```bash
123
+ IBMI_TRANSPORT=http
124
+ IBMI_SYSTEM=your-ibmi-host
125
+ IBMI_USER=your-username
126
+ IBMI_PASS=your-password
127
+ IBMI_PORT=57700
128
+ IBMI_DATABASE=*LOCAL
129
+ ```
130
+
131
+ Then generate the typed client from your schema:
132
+
133
+ ```bash
134
+ npm run generate
135
+ ```
136
+
137
+ This reads `schema.ibmi` and writes a fully typed TypeScript client to `src/generated/ibmi/`.
138
+
139
+ ---
140
+
141
+ ## Transport notes
142
+
143
+ | Transport | Notes |
144
+ |---|---|
145
+ | **HTTP** | Recommended for development. Requires an SSH tunnel: `ssh -L 57700:localhost:57700 user@ibmi-host` |
146
+ | **HTTPS** | Same as HTTP but TLS. Default port 47700. |
147
+ | **SSH** | Direct connection. Install the `ssh2` peer dep: `npm install ssh2`. Also install `xmlservice-utils` on IBM i. |
148
+ | **ODBC** | Native DB2 pool. Best for production. Install `odbc` and the IBM i Access ODBC Driver. |
149
+
150
+ ---
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env node
2
+ import * as p from '@clack/prompts';
3
+ import { execSync } from 'node:child_process';
4
+ import { existsSync } from 'node:fs';
5
+ import { resolve } from 'node:path';
6
+ import { scaffold } from './scaffold.js';
7
+ async function main() {
8
+ // Accept project name as positional arg: npx create-openibm my-app
9
+ const argName = process.argv[2];
10
+ console.log();
11
+ p.intro(' ◆ create-openibm ');
12
+ // ── Project name ──────────────────────────────────────────────────────────
13
+ let projectName;
14
+ if (argName && !argName.startsWith('-')) {
15
+ projectName = argName;
16
+ }
17
+ else {
18
+ const res = await p.text({
19
+ message: 'Project name',
20
+ placeholder: 'my-ibmi-app',
21
+ validate: v => (v.trim() ? undefined : 'Project name is required'),
22
+ });
23
+ if (p.isCancel(res)) {
24
+ p.cancel('Cancelled.');
25
+ process.exit(0);
26
+ }
27
+ projectName = res.trim();
28
+ }
29
+ const dir = resolve(process.cwd(), projectName);
30
+ if (existsSync(dir)) {
31
+ const overwrite = await p.confirm({
32
+ message: `Directory "${projectName}" already exists. Continue anyway?`,
33
+ initialValue: false,
34
+ });
35
+ if (p.isCancel(overwrite) || !overwrite) {
36
+ p.cancel('Aborted.');
37
+ process.exit(0);
38
+ }
39
+ }
40
+ // ── Framework ─────────────────────────────────────────────────────────────
41
+ const framework = await p.select({
42
+ message: 'Framework',
43
+ options: [
44
+ { value: 'basic', label: 'None', hint: 'plain Node.js script' },
45
+ { value: 'express', label: 'Express', hint: 'lightweight HTTP server' },
46
+ { value: 'nestjs', label: 'NestJS', hint: 'enterprise-grade framework with DI' },
47
+ ],
48
+ });
49
+ if (p.isCancel(framework)) {
50
+ p.cancel('Cancelled.');
51
+ process.exit(0);
52
+ }
53
+ // ── Transport ─────────────────────────────────────────────────────────────
54
+ const transport = await p.select({
55
+ message: 'IBM i transport',
56
+ options: [
57
+ { value: 'http', label: 'HTTP', hint: 'SSH tunnel — recommended for development' },
58
+ { value: 'https', label: 'HTTPS', hint: 'secure HTTP, port 47700' },
59
+ { value: 'ssh', label: 'SSH', hint: 'direct connection — install ssh2 peer dep' },
60
+ { value: 'odbc', label: 'ODBC', hint: 'native DB2 pool — recommended for production' },
61
+ ],
62
+ });
63
+ if (p.isCancel(transport)) {
64
+ p.cancel('Cancelled.');
65
+ process.exit(0);
66
+ }
67
+ // ── Starter examples ──────────────────────────────────────────────────────
68
+ const features = await p.multiselect({
69
+ message: 'Starter examples (space to toggle, enter to confirm)',
70
+ options: [
71
+ { value: 'program', label: 'Program call', hint: 'XMLSERVICE *PGM example in schema.ibmi' },
72
+ { value: 'table', label: 'Table query', hint: 'DB2 query builder example in schema.ibmi' },
73
+ ],
74
+ required: false,
75
+ });
76
+ if (p.isCancel(features)) {
77
+ p.cancel('Cancelled.');
78
+ process.exit(0);
79
+ }
80
+ // ── Package manager ───────────────────────────────────────────────────────
81
+ const pkgManager = await p.select({
82
+ message: 'Package manager',
83
+ options: [
84
+ { value: 'npm', label: 'npm' },
85
+ { value: 'pnpm', label: 'pnpm' },
86
+ { value: 'yarn', label: 'Yarn' },
87
+ ],
88
+ });
89
+ if (p.isCancel(pkgManager)) {
90
+ p.cancel('Cancelled.');
91
+ process.exit(0);
92
+ }
93
+ // ── Extras ────────────────────────────────────────────────────────────────
94
+ const gitInit = await p.confirm({ message: 'Initialize a git repository?' });
95
+ if (p.isCancel(gitInit)) {
96
+ p.cancel('Cancelled.');
97
+ process.exit(0);
98
+ }
99
+ const install = await p.confirm({ message: 'Install dependencies now?' });
100
+ if (p.isCancel(install)) {
101
+ p.cancel('Cancelled.');
102
+ process.exit(0);
103
+ }
104
+ // ── Scaffold ──────────────────────────────────────────────────────────────
105
+ const answers = {
106
+ projectName,
107
+ framework: framework,
108
+ transport: transport,
109
+ features: features,
110
+ pkgManager: pkgManager,
111
+ gitInit: gitInit,
112
+ install: install,
113
+ };
114
+ const s = p.spinner();
115
+ s.start('Scaffolding project…');
116
+ scaffold(answers, dir);
117
+ s.stop('Files written.');
118
+ // ── Git init ──────────────────────────────────────────────────────────────
119
+ if (answers.gitInit) {
120
+ try {
121
+ execSync('git init', { cwd: dir, stdio: 'ignore' });
122
+ execSync('git add -A', { cwd: dir, stdio: 'ignore' });
123
+ }
124
+ catch { /* git not available */ }
125
+ }
126
+ // ── Install deps ──────────────────────────────────────────────────────────
127
+ if (answers.install) {
128
+ const is = p.spinner();
129
+ is.start(`Installing with ${pkgManager}…`);
130
+ try {
131
+ const cmd = pkgManager === 'yarn' ? 'yarn' : `${pkgManager} install`;
132
+ execSync(cmd, { cwd: dir, stdio: 'pipe' });
133
+ is.stop('Dependencies installed.');
134
+ }
135
+ catch {
136
+ is.stop('Install failed — run it manually.');
137
+ }
138
+ }
139
+ // ── Done ──────────────────────────────────────────────────────────────────
140
+ const pm = answers.pkgManager;
141
+ const run = pm === 'npm' ? 'npm run' : pm;
142
+ p.outro([
143
+ 'Done! Next steps:',
144
+ '',
145
+ ` cd ${dir}`,
146
+ ` cp .env.example .env`,
147
+ ` # fill in IBMI_SYSTEM, IBMI_USER, IBMI_PASS`,
148
+ ...(answers.install ? [] : [` ${pm} install`]),
149
+ ` ${run} generate`,
150
+ ` ${run} dev`,
151
+ ].join('\n'));
152
+ }
153
+ main().catch(e => {
154
+ console.error(e);
155
+ process.exit(1);
156
+ });
@@ -0,0 +1,12 @@
1
+ export interface Answers {
2
+ projectName: string;
3
+ framework: 'basic' | 'express' | 'nestjs';
4
+ transport: 'http' | 'https' | 'ssh' | 'odbc';
5
+ features: ('program' | 'table')[];
6
+ pkgManager: 'npm' | 'pnpm' | 'yarn';
7
+ gitInit: boolean;
8
+ install: boolean;
9
+ }
10
+ /** Relative file path → file content */
11
+ export type FileMap = Record<string, string>;
12
+ export declare function scaffold(answers: Answers, outDir: string): void;
@@ -0,0 +1,22 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { baseFiles } from './templates/base.js';
4
+ import { basicFiles } from './templates/basic.js';
5
+ import { expressFiles } from './templates/express.js';
6
+ import { nestjsFiles } from './templates/nestjs.js';
7
+ const frameworkFiles = {
8
+ basic: basicFiles,
9
+ express: expressFiles,
10
+ nestjs: nestjsFiles,
11
+ };
12
+ export function scaffold(answers, outDir) {
13
+ const files = {
14
+ ...baseFiles(answers),
15
+ ...frameworkFiles[answers.framework](answers),
16
+ };
17
+ for (const [relPath, content] of Object.entries(files)) {
18
+ const abs = join(outDir, relPath);
19
+ mkdirSync(dirname(abs), { recursive: true });
20
+ writeFileSync(abs, content, 'utf8');
21
+ }
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { Answers, FileMap } from '../scaffold.js';
2
+ export declare function baseFiles(a: Answers): FileMap;
@@ -0,0 +1,58 @@
1
+ export function baseFiles(a) {
2
+ return {
3
+ 'schema.ibmi': schemaIbmi(a),
4
+ '.env.example': envExample(a),
5
+ '.gitignore': gitignore(),
6
+ };
7
+ }
8
+ // ── schema.ibmi ───────────────────────────────────────────────────────────────
9
+ function schemaIbmi(a) {
10
+ const blocks = [
11
+ `datasource ibmi {`,
12
+ ` transport = env("IBMI_TRANSPORT")`,
13
+ ` system = env("IBMI_SYSTEM")`,
14
+ `}`,
15
+ ``,
16
+ `generator client {`,
17
+ ` output = "./src/generated/ibmi"`,
18
+ `}`,
19
+ ];
20
+ if (a.features.includes('program')) {
21
+ blocks.push(``, `// RPG/COBOL program — JS name maps to actual IBM i program via @map`, `program SimpleCalc @map("SIMPLECALC") {`, ` library = "MYLIB"`, ` input PackedDecimal(15, 0) @in`, ` output PackedDecimal(16, 0) @out`, `}`);
22
+ }
23
+ if (a.features.includes('table')) {
24
+ blocks.push(``, `// DB2 table — JS field names map to real column names via @map`, `table Customer @map("QIWS.QCUSTCDT") {`, ` customerId Int @id @map("CUSNUM")`, ` lastName Char(8) @map("LSTNAM")`, ` initials Char(3) @map("INIT")`, ` city Char(6) @map("CITY")`, ` state Char(2) @map("STATE")`, ` creditLimit Decimal(6, 0) @map("CDTLMT")`, ` balanceDue Decimal(6, 2) @map("BALDUE")`, `}`);
25
+ }
26
+ return blocks.join('\n') + '\n';
27
+ }
28
+ // ── .env.example ──────────────────────────────────────────────────────────────
29
+ function envExample(a) {
30
+ const lines = [
31
+ `IBMI_TRANSPORT=${a.transport}`,
32
+ `IBMI_SYSTEM=your-ibmi-host`,
33
+ `IBMI_USER=your-username`,
34
+ `IBMI_PASS=your-password`,
35
+ ];
36
+ if (a.transport === 'http' || a.transport === 'https') {
37
+ const defaultPort = a.transport === 'https' ? '47700' : '57700';
38
+ lines.push(`IBMI_PORT=${defaultPort}`);
39
+ lines.push(`IBMI_DATABASE=*LOCAL`);
40
+ }
41
+ return lines.join('\n') + '\n';
42
+ }
43
+ // ── .gitignore ────────────────────────────────────────────────────────────────
44
+ function gitignore() {
45
+ return [
46
+ 'node_modules/',
47
+ 'dist/',
48
+ '.env',
49
+ '*.tsbuildinfo',
50
+ '',
51
+ '# Generated IBM i client — re-create with: npm run generate',
52
+ 'src/generated/',
53
+ '',
54
+ '# OS',
55
+ '.DS_Store',
56
+ 'Thumbs.db',
57
+ ].join('\n') + '\n';
58
+ }
@@ -0,0 +1,11 @@
1
+ import type { Answers, FileMap } from '../scaffold.js';
2
+ export declare function basicFiles(a: Answers): FileMap;
3
+ export declare function clientConfigBlock(a: Answers): string;
4
+ /**
5
+ * scripts/link-dev.mjs — shared by all templates.
6
+ * Uses `pnpm add file:...` so pnpm installs ALL deps in one shot while
7
+ * pulling @openibm/* from the globally linked monorepo packages.
8
+ */
9
+ export declare function linkDevScriptMjs(): string;
10
+ /** Zod-based IBM i data-type validators — shared by basic + express templates */
11
+ export declare function ibmiValidatorsTs(): string;