@karmaniverous/smoz 0.1.3 → 0.1.5

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.
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- #!/usr/bin/env node
3
2
  'use strict';
4
3
 
5
4
  var fs = require('node:fs');
@@ -11,6 +10,7 @@ var chokidar = require('chokidar');
11
10
  var node_child_process = require('node:child_process');
12
11
  var os = require('node:os');
13
12
 
13
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
14
  /**
15
15
  * SMOZ CLI: add
16
16
  *
@@ -823,8 +823,12 @@ const walk = async (dir, out = []) => {
823
823
  return out;
824
824
  };
825
825
  const resolveTemplatesBase = () => {
826
- // Resolve the package root (works both in dev and when installed)
827
- const pkgRoot = packageDirectory.packageDirectorySync() ?? process.cwd();
826
+ // Resolve the templates folder from the CLI package install root,
827
+ // not the caller’s project root. This makes --template <name> work
828
+ // consistently whether smoz is run from a consuming app or this repo.
829
+ // Anchor discovery to this module’s directory.
830
+ const here = path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
831
+ const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd(); // conservative fallback
828
832
  return path.resolve(pkgRoot, 'templates');
829
833
  };
830
834
  const readJson = async (file) => {
@@ -1044,8 +1048,7 @@ const runInit = async (root, template = 'minimal', opts) => {
1044
1048
  /**
1045
1049
  * SMOZ CLI — version/signature + register/add
1046
1050
  *
1047
- * - Default: print project signature (version, Node, repo root, stanPath, config presence)
1048
- * - register: one-shot — generate app/generated/register.*.ts from app/functions/**
1051
+ * - Default: print project signature (version, Node, repo root, stanPath, config presence) * - register: one-shot — generate app/generated/register.*.ts from app/functions/**
1049
1052
  * - openapi: one-shot — run the project’s OpenAPI builder
1050
1053
  * - dev: watch loop orchestrator for register/openapi and optional local serving
1051
1054
  * - add: scaffold a new function skeleton under app/functions
package/package.json CHANGED
@@ -185,5 +185,5 @@
185
185
  "templates:lint": "eslint --fix -c templates/.check/eslint.templates.config.ts \"templates/**/*.{ts,tsx,js,jsx}\" && eslint --fix --no-ignore templates/.check/eslint.templates.config.ts"
186
186
  },
187
187
  "type": "module",
188
- "version": "0.1.3"
188
+ "version": "0.1.5"
189
189
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "dependencies": {
3
+ "zod": "^4.1.5",
4
+ "@middy/core": "^6.4.4"
5
+ },
6
+ "devDependencies": {
7
+ "typescript": "^5.9.2",
8
+ "typescript-eslint": "^8.41.0",
9
+ "eslint": "^9.34.0",
10
+ "eslint-config-prettier": "^10.1.8",
11
+ "eslint-plugin-prettier": "^5.5.4",
12
+ "eslint-plugin-simple-import-sort": "^12.1.1",
13
+ "vitest": "^3.2.4",
14
+ "typedoc": "^0.28.11"
15
+ },
16
+ "scripts": {
17
+ "typecheck": "tsc -p tsconfig.json --noEmit",
18
+ "lint": "eslint .",
19
+ "lint:fix": "eslint --fix ."
20
+ }
21
+ }
@@ -0,0 +1,50 @@
1
+ import { join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ import { App, baseEventTypeMapSchema, toPosixPath } from '@karmaniverous/smoz';
5
+ import { z } from 'zod';
6
+
7
+ // Derive the app root as the parent directory of app/config/
8
+ export const APP_ROOT_ABS = toPosixPath(
9
+ fileURLToPath(new URL('..', import.meta.url)),
10
+ );
11
+
12
+ export const app = App.create({
13
+ appRootAbs: APP_ROOT_ABS,
14
+ globalParamsSchema: z.object({
15
+ PROFILE: z.string(),
16
+ REGION: z.string(),
17
+ SERVICE_NAME: z.string(),
18
+ }),
19
+ stageParamsSchema: z.object({
20
+ STAGE: z.string(),
21
+ }),
22
+ eventTypeMapSchema: baseEventTypeMapSchema,
23
+ serverless: {
24
+ httpContextEventMap: {
25
+ my: {}, // place a Cognito authorizer here if needed
26
+ private: { private: true },
27
+ public: {},
28
+ },
29
+ defaultHandlerFileName: 'handler',
30
+ defaultHandlerFileExport: 'handler',
31
+ },
32
+ global: {
33
+ params: {
34
+ PROFILE: 'dev',
35
+ REGION: 'us-east-1',
36
+ SERVICE_NAME: 'my-smoz-app',
37
+ },
38
+ envKeys: ['REGION', 'SERVICE_NAME'] as const,
39
+ },
40
+ stage: {
41
+ params: {
42
+ dev: { STAGE: 'dev' },
43
+ },
44
+ envKeys: ['STAGE'] as const,
45
+ },
46
+ });
47
+
48
+ export const ENDPOINTS_ROOT_REST = toPosixPath(
49
+ join(APP_ROOT_ABS, 'functions', 'rest'),
50
+ );
@@ -0,0 +1,44 @@
1
+ import * as path from 'node:path';
2
+
3
+ import * as fs from 'fs-extra';
4
+ import { packageDirectorySync } from 'pkg-dir';
5
+ import { createDocument } from 'zod-openapi';
6
+
7
+ import { app } from '@/app/config/app.config';
8
+ /**
9
+ * Template note:
10
+ * - Templates do NOT commit generated register files under app/generated; they
11
+ * are declared via ambient types so TypeScript can typecheck without artifacts.
12
+ * - To ensure side effects still run (endpoint registration) and to satisfy
13
+ * noUncheckedSideEffectImports, import the register module as a namespace and
14
+ * reference it via `void`.
15
+ * - In real apps, `smoz init` seeds placeholders and `smoz register` rewrites
16
+ * app/generated/register.*.ts at author time.
17
+ */
18
+ import * as __register_openapi from '@/app/generated/register.openapi';
19
+ void __register_openapi;
20
+ console.log('Generating OpenAPI document...');
21
+
22
+ const paths = app.buildAllOpenApiPaths();
23
+ export const doc = createDocument({
24
+ openapi: '3.1.0',
25
+ servers: [{ description: 'Dev', url: 'http://localhost' }],
26
+ info: {
27
+ title: process.env.npm_package_name ?? 'smoz-app',
28
+ version: process.env.npm_package_version ?? '0.0.0',
29
+ },
30
+ paths,
31
+ });
32
+
33
+ const pkgDir = packageDirectorySync();
34
+ if (!pkgDir) {
35
+ throw new Error('Could not resolve package root directory');
36
+ }
37
+ const outDir = path.join(pkgDir, 'app', 'generated');
38
+ fs.ensureDirSync(outDir);
39
+ fs.writeFileSync(
40
+ path.join(outDir, 'openapi.json'),
41
+ JSON.stringify(doc, null, 2),
42
+ );
43
+
44
+ console.log('Done!');
@@ -0,0 +1,18 @@
1
+ import type { z } from 'zod';
2
+
3
+ import type { responseSchema } from './lambda';
4
+ import { fn } from './lambda';
5
+
6
+ type Response = z.infer<typeof responseSchema>;
7
+
8
+ type FnHandlerApi<T> = {
9
+ handler: (impl: () => Promise<T> | T) => (...args: unknown[]) => Promise<T>;
10
+ };
11
+
12
+ const reg = fn as unknown as FnHandlerApi<Response>;
13
+
14
+ export const handler = reg.handler(async () => {
15
+ const res: Response = { ok: true };
16
+ await Promise.resolve(); // satisfy require-await without adding complexity
17
+ return res;
18
+ });
@@ -0,0 +1,28 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { toPosixPath } from '@karmaniverous/smoz';
4
+ import { z } from 'zod';
5
+
6
+ import { app, APP_ROOT_ABS } from '@/app/config/app.config';
7
+
8
+ export const eventSchema = z.object({}).optional();
9
+ export const responseSchema = z.object({ ok: z.boolean() });
10
+ type FnApi = {
11
+ handler: <T>(
12
+ impl: () => Promise<T> | T,
13
+ ) => (...args: unknown[]) => Promise<T>;
14
+ openapi: (op: unknown) => void;
15
+ };
16
+
17
+ export const fn = app.defineFunction({
18
+ functionName: 'hello_get',
19
+ eventType: 'rest',
20
+ httpContexts: ['public'],
21
+ method: 'get',
22
+ basePath: 'hello',
23
+ contentType: 'application/json',
24
+ eventSchema,
25
+ responseSchema,
26
+ callerModuleUrl: import.meta.url,
27
+ endpointsRootAbs: toPosixPath(join(APP_ROOT_ABS, 'functions', 'rest')),
28
+ }) as unknown as FnApi;
@@ -0,0 +1,19 @@
1
+ import { fn, responseSchema } from './lambda';
2
+
3
+ type FnOpenApiApi = {
4
+ openapi: (op: unknown) => void;
5
+ };
6
+
7
+ const reg = fn as unknown as FnOpenApiApi;
8
+
9
+ reg.openapi({
10
+ summary: 'Hello',
11
+ description: 'Return a simple OK payload.',
12
+ responses: {
13
+ 200: {
14
+ description: 'Ok',
15
+ content: { 'application/json': { schema: responseSchema } },
16
+ },
17
+ },
18
+ tags: ['public'],
19
+ });
@@ -0,0 +1,20 @@
1
+ import type { z } from 'zod';
2
+
3
+ import openapiDoc from '@/app/generated/openapi.json';
4
+
5
+ import type { responseSchema } from './lambda';
6
+ import { fn } from './lambda';
7
+
8
+ type Response = z.infer<typeof responseSchema>;
9
+
10
+ type FnHandlerApi<T> = {
11
+ handler: (impl: () => Promise<T> | T) => (...args: unknown[]) => Promise<T>;
12
+ };
13
+
14
+ const reg = fn as unknown as FnHandlerApi<Response>;
15
+
16
+ export const handler = reg.handler(async () => {
17
+ // Trivial await to mirror minimal template style
18
+ await Promise.resolve();
19
+ return openapiDoc as Response;
20
+ });
@@ -0,0 +1,29 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { toPosixPath } from '@karmaniverous/smoz';
4
+ import { z } from 'zod';
5
+
6
+ import { app, APP_ROOT_ABS } from '@/app/config/app.config';
7
+
8
+ export const eventSchema = z.any().optional();
9
+ export const responseSchema = z.unknown();
10
+
11
+ type FnApi = {
12
+ handler: <T>(
13
+ impl: () => Promise<T> | T,
14
+ ) => (...args: unknown[]) => Promise<T>;
15
+ openapi: (op: unknown) => void;
16
+ };
17
+
18
+ export const fn = app.defineFunction({
19
+ functionName: 'openapi_get',
20
+ eventType: 'rest',
21
+ httpContexts: ['public'],
22
+ method: 'get',
23
+ basePath: 'openapi',
24
+ contentType: 'application/json',
25
+ eventSchema,
26
+ responseSchema,
27
+ callerModuleUrl: import.meta.url,
28
+ endpointsRootAbs: toPosixPath(join(APP_ROOT_ABS, 'functions', 'rest')),
29
+ }) as unknown as FnApi;
@@ -0,0 +1,20 @@
1
+ import { fn, responseSchema } from './lambda';
2
+
3
+ type FnOpenApiApi = {
4
+ openapi: (op: unknown) => void;
5
+ };
6
+
7
+ const reg = fn as unknown as FnOpenApiApi;
8
+
9
+ reg.openapi({
10
+ summary: 'Get OpenAPI document',
11
+ description:
12
+ 'Return the OpenAPI 3.1 document. Generate it via "npm run openapi".',
13
+ responses: {
14
+ 200: {
15
+ description: 'OpenAPI document',
16
+ content: { 'application/json': { schema: responseSchema } },
17
+ },
18
+ },
19
+ tags: ['openapi'],
20
+ });
@@ -0,0 +1,7 @@
1
+ import { fn } from './lambda';
2
+
3
+ export const handler = fn.handler((event) => {
4
+ // process SQS batch event
5
+ void event;
6
+ // no response payload (void)
7
+ });
@@ -0,0 +1,24 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { toPosixPath } from '@karmaniverous/smoz';
4
+ import { z } from 'zod';
5
+
6
+ import { app, APP_ROOT_ABS } from '@/app/config/app.config';
7
+
8
+ export const eventSchema = z.any();
9
+ export const responseSchema = z.void();
10
+
11
+ type FnApi = {
12
+ handler: <T>(
13
+ impl: (e: unknown) => Promise<T> | T,
14
+ ) => (...args: unknown[]) => Promise<T>;
15
+ serverless: (extras: unknown) => void;
16
+ };
17
+
18
+ export const fn = app.defineFunction({
19
+ eventType: 'sqs',
20
+ eventSchema,
21
+ responseSchema,
22
+ callerModuleUrl: import.meta.url,
23
+ endpointsRootAbs: toPosixPath(join(APP_ROOT_ABS, 'functions', 'sqs')),
24
+ }) as unknown as FnApi;
@@ -0,0 +1,8 @@
1
+ import { fn } from './lambda';
2
+
3
+ // Attach SQS trigger (replace ARN with your queue)
4
+ fn.serverless([
5
+ {
6
+ sqs: { arn: 'arn:aws:sqs:us-east-1:000000000000:my-queue' },
7
+ },
8
+ ]);
@@ -0,0 +1,8 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": { "title": "smoz-app", "version": "0.0.0" },
4
+ "servers": [
5
+ { "description": "Dev", "url": "http://localhost" }
6
+ ],
7
+ "paths": {}
8
+ }
@@ -0,0 +1,33 @@
1
+ import type { AWS } from '@serverless/typescript';
2
+
3
+ import { app } from '@/app/config/app.config';
4
+ /**
5
+ * Template note:
6
+ * - Templates do NOT commit generated register files under app/generated; they
7
+ * are declared via ambient types so TypeScript can typecheck without artifacts.
8
+ * - To ensure side effects still run (endpoint/serverless registration) and to
9
+ * satisfy noUncheckedSideEffectImports, import register modules as namespaces
10
+ * and reference them via `void`.
11
+ * - In real apps, `smoz init` seeds placeholders and `smoz register` rewrites
12
+ * app/generated/register.*.ts at author time.
13
+ */
14
+ import * as __register_functions from '@/app/generated/register.functions';
15
+ import * as __register_serverless from '@/app/generated/register.serverless';
16
+ void __register_functions;
17
+ void __register_serverless;
18
+
19
+ const config: AWS = {
20
+ service: '${param:SERVICE_NAME}',
21
+ frameworkVersion: '4',
22
+ stages: app.stages as NonNullable<AWS['stages']>,
23
+ provider: {
24
+ name: 'aws',
25
+ region: '${param:REGION}' as NonNullable<AWS['provider']['region']>,
26
+ runtime: 'nodejs22.x',
27
+ environment: app.environment as NonNullable<AWS['provider']['environment']>,
28
+ stage: '${opt:stage, "dev"}',
29
+ },
30
+ functions: app.buildAllServerlessFunctions() as NonNullable<AWS['functions']>,
31
+ };
32
+
33
+ export default config;
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "rootDir": ".",
5
+ "outDir": ".tsbuild",
6
+ "paths": {
7
+ "@/*": ["*"],
8
+ "@karmaniverous/smoz": ["../../.stan/dist/index.d.ts"]
9
+ },
10
+ "typeRoots": ["../../node_modules/@types", "./node_modules/@types"],
11
+ "tsBuildInfoFile": ".tsbuild/tsconfig.tsbuildinfo"
12
+ },
13
+ "exclude": [
14
+ ".serverless/**",
15
+ "node_modules/**",
16
+ "app/generated/**",
17
+ "dist/**"
18
+ ],
19
+ "extends": "../project/tsconfig.base.json",
20
+ "include": [
21
+ "**/*.ts",
22
+ "**/*.tsx",
23
+ "**/*.d.ts"
24
+ ]
25
+ }
@@ -0,0 +1,11 @@
1
+ // Ambient declarations to satisfy template typechecking without requiring
2
+ // generated register files to exist on disk.
3
+ declare module '@/app/generated/register.functions' {
4
+ export {};
5
+ }
6
+ declare module '@/app/generated/register.openapi' {
7
+ export {};
8
+ }
9
+ declare module '@/app/generated/register.serverless' {
10
+ export {};
11
+ }
@@ -0,0 +1,22 @@
1
+ import type { z } from 'zod';
2
+
3
+ // Import the document from its real generated location.
4
+ // The template ships a placeholder at app/generated/openapi.json.
5
+ import openapiDoc from '@/app/generated/openapi.json';
6
+
7
+ import type { responseSchema } from './lambda';
8
+ import { fn } from './lambda';
9
+
10
+ type Response = z.infer<typeof responseSchema>;
11
+ type FnHandlerApi<T> = {
12
+ handler: (impl: () => Promise<T> | T) => (...args: unknown[]) => Promise<T>;
13
+ };
14
+
15
+ const reg = fn as unknown as FnHandlerApi<Response>;
16
+
17
+ export const handler = reg.handler(async () => {
18
+ // Tip: run `npm run openapi` to regenerate this file.
19
+ // Trivial await keeps parity with prior style.
20
+ await Promise.resolve();
21
+ return openapiDoc as Response;
22
+ });
@@ -0,0 +1,29 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { toPosixPath } from '@karmaniverous/smoz';
4
+ import { z } from 'zod';
5
+
6
+ import { app, APP_ROOT_ABS } from '@/app/config/app.config';
7
+
8
+ export const eventSchema = z.any().optional();
9
+ export const responseSchema = z.unknown();
10
+
11
+ type FnApi = {
12
+ handler: <T>(
13
+ impl: () => Promise<T> | T,
14
+ ) => (...args: unknown[]) => Promise<T>;
15
+ openapi: (op: unknown) => void;
16
+ };
17
+
18
+ export const fn = app.defineFunction({
19
+ functionName: 'openapi_get',
20
+ eventType: 'rest',
21
+ httpContexts: ['public'],
22
+ method: 'get',
23
+ basePath: 'openapi',
24
+ contentType: 'application/json',
25
+ eventSchema,
26
+ responseSchema,
27
+ callerModuleUrl: import.meta.url,
28
+ endpointsRootAbs: toPosixPath(join(APP_ROOT_ABS, 'functions', 'rest')),
29
+ }) as unknown as FnApi;
@@ -0,0 +1,20 @@
1
+ import { fn, responseSchema } from './lambda';
2
+
3
+ type FnOpenApiApi = {
4
+ openapi: (op: unknown) => void;
5
+ };
6
+
7
+ const reg = fn as unknown as FnOpenApiApi;
8
+
9
+ reg.openapi({
10
+ summary: 'Get OpenAPI document',
11
+ description:
12
+ 'Return the OpenAPI 3.1 document. Generate it via "npm run openapi".',
13
+ responses: {
14
+ 200: {
15
+ description: 'OpenAPI document',
16
+ content: { 'application/json': { schema: responseSchema } },
17
+ },
18
+ },
19
+ tags: ['openapi'],
20
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": { "title": "smoz-app", "version": "0.0.0" },
4
+ "servers": [
5
+ { "description": "Dev", "url": "http://localhost" }
6
+ ],
7
+ "paths": {}
8
+ }