@karmaniverous/smoz 0.1.8 → 0.2.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.
Files changed (51) hide show
  1. package/dist/cli/index.cjs +191 -59
  2. package/package.json +2 -2
  3. package/templates/{minimal → default}/app/config/app.config.ts +50 -50
  4. package/templates/{minimal → default}/app/config/openapi.ts +45 -45
  5. package/templates/{full → default}/app/functions/rest/hello/get/handler.ts +18 -18
  6. package/templates/{full → default}/app/functions/rest/hello/get/lambda.ts +28 -28
  7. package/templates/{minimal → default}/app/functions/rest/hello/get/openapi.ts +23 -23
  8. package/templates/{minimal → default}/app/functions/rest/openapi/get/handler.ts +22 -22
  9. package/templates/{minimal → default}/app/functions/rest/openapi/get/lambda.ts +29 -29
  10. package/templates/{full → default}/app/functions/rest/openapi/get/openapi.ts +20 -20
  11. package/templates/{project → default}/eslint.config.ts +6 -15
  12. package/templates/default/package.json +44 -0
  13. package/templates/default/serverless.ts +113 -0
  14. package/templates/default/tsconfig.eslint.json +13 -0
  15. package/templates/{minimal → default}/types/registers.d.ts +11 -11
  16. package/templates/.check/eslint.minimal.config.ts +0 -41
  17. package/templates/.check/eslint.templates.config.ts +0 -60
  18. package/templates/.check/tsconfig.eslintconfig.json +0 -6
  19. package/templates/.check/tsconfig.minimal.json +0 -14
  20. package/templates/.manifests/package.full.json +0 -22
  21. package/templates/.manifests/package.minimal.json +0 -22
  22. package/templates/.manifests/package.project.json +0 -24
  23. package/templates/full/app/config/app.config.ts +0 -50
  24. package/templates/full/app/config/openapi.ts +0 -44
  25. package/templates/full/app/functions/rest/hello/get/openapi.ts +0 -19
  26. package/templates/full/app/functions/rest/openapi/get/handler.ts +0 -20
  27. package/templates/full/app/functions/rest/openapi/get/lambda.ts +0 -29
  28. package/templates/full/app/functions/sqs/tick/handler.ts +0 -7
  29. package/templates/full/app/functions/sqs/tick/lambda.ts +0 -24
  30. package/templates/full/app/functions/sqs/tick/serverless.ts +0 -8
  31. package/templates/full/serverless.ts +0 -33
  32. package/templates/full/tsconfig.json +0 -25
  33. package/templates/full/types/registers.d.ts +0 -11
  34. package/templates/minimal/app/functions/rest/hello/get/handler.ts +0 -18
  35. package/templates/minimal/app/functions/rest/hello/get/lambda.ts +0 -28
  36. package/templates/minimal/app/functions/rest/openapi/get/openapi.ts +0 -20
  37. package/templates/minimal/app/generated/openapi.json +0 -8
  38. package/templates/minimal/serverless.ts +0 -34
  39. package/templates/minimal/tsconfig.json +0 -25
  40. /package/templates/{project → default}/.prettierrc.json +0 -0
  41. /package/templates/{project → default}/.vscode/extensions.json +0 -0
  42. /package/templates/{project → default}/.vscode/settings.json +0 -0
  43. /package/templates/{project → default}/README.md +0 -0
  44. /package/templates/{full → default}/app/generated/openapi.json +0 -0
  45. /package/templates/{project → default}/gitignore +0 -0
  46. /package/templates/{project → default}/test/smoke.test.ts +0 -0
  47. /package/templates/{project → default}/tsconfig.base.json +0 -0
  48. /package/templates/{project → default}/tsconfig.json +0 -0
  49. /package/templates/{project → default}/tsdoc.json +0 -0
  50. /package/templates/{project → default}/typedoc.json +0 -0
  51. /package/templates/{project → default}/vitest.config.ts +0 -0
@@ -9,6 +9,8 @@ var node_url = require('node:url');
9
9
  var chokidar = require('chokidar');
10
10
  var node_child_process = require('node:child_process');
11
11
  var os = require('node:os');
12
+ var node_process = require('node:process');
13
+ var promises = require('node:readline/promises');
12
14
 
13
15
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
16
  /**
@@ -794,8 +796,7 @@ const launchInline = async (root, opts) => {
794
796
  * smoz init
795
797
  *
796
798
  * Scaffolds a new project from packaged templates.
797
- * - Copies ./templates/project/ into the target root (shared boilerplate)
798
- * - Copies ./templates/<template>/ into the target root (default: minimal)
799
+ * - Copies ./templates/<template>/ into the target root (default: default)
799
800
  * - Seeds app/generated/register.*.ts (empty modules) if missing * - Idempotent: copy-if-absent; if a file exists, writes <name>.example alongside
800
801
  * - Additive merge of template manifest (deps/devDeps/scripts) into package.json
801
802
  * - Optional dependency installation via --install[=<pm>]
@@ -808,6 +809,25 @@ const writeIfAbsent = async (outFile, content) => {
808
809
  await fs.promises.writeFile(outFile, content, 'utf8');
809
810
  return { created: true };
810
811
  };
812
+ const askConflict = async (rl, filePath) => {
813
+ const q = `File exists: ${toPosix(filePath)}\n` +
814
+ `Choose: [o]verwrite, [e]xample, [s]kip, [O]verwrite all, [E]xample all, [S]kip all: `;
815
+ // Single key selection for simplicity
816
+ const ans = (await rl.question(q)).trim();
817
+ if (/^o$/.test(ans))
818
+ return 'overwrite';
819
+ if (/^e$/.test(ans))
820
+ return 'example';
821
+ if (/^s$/.test(ans))
822
+ return 'skip';
823
+ if (/^O$/.test(ans))
824
+ return 'all-overwrite';
825
+ if (/^E$/.test(ans))
826
+ return 'all-example';
827
+ if (/^S$/.test(ans))
828
+ return 'all-skip';
829
+ return 'example';
830
+ };
811
831
  const walk = async (dir, out = []) => {
812
832
  const entries = await fs.promises.readdir(dir, { withFileTypes: true });
813
833
  for (const ent of entries) {
@@ -928,48 +948,106 @@ const mergeAdditive = (target, source) => {
928
948
  target.scripts = scriptsOut;
929
949
  return merged;
930
950
  };
931
- const copyDirIdempotent = async (srcDir, dstRoot, created, skipped, examples) => {
951
+ const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples, opts) => {
932
952
  const files = await walk(srcDir);
953
+ let sticky;
933
954
  for (const abs of files) {
934
955
  const rel = path.relative(srcDir, abs);
935
956
  const dest = path.resolve(dstRoot, rel);
936
957
  const data = await fs.promises.readFile(abs, 'utf8');
937
- if (fs.existsSync(dest)) {
938
- const ex = `${dest}.example`;
939
- if (!fs.existsSync(ex)) {
940
- await writeIfAbsent(ex, data);
941
- examples.push(path.posix.normalize(ex));
942
- }
943
- else {
944
- skipped.push(path.posix.normalize(dest));
945
- }
946
- }
947
- else {
958
+ if (!fs.existsSync(dest)) {
948
959
  const { created: c } = await writeIfAbsent(dest, data);
949
960
  if (c)
950
961
  created.push(path.posix.normalize(dest));
951
962
  else
952
963
  skipped.push(path.posix.normalize(dest));
964
+ continue;
965
+ }
966
+ // Conflict
967
+ let decision = opts.conflict === 'ask' ? 'example' : opts.conflict;
968
+ if (opts.conflict === 'ask' && opts.rl && !sticky) {
969
+ const ans = await askConflict(opts.rl, dest);
970
+ if (ans === 'all-overwrite') {
971
+ sticky = 'overwrite';
972
+ }
973
+ else if (ans === 'all-example') {
974
+ sticky = 'example';
975
+ }
976
+ else if (ans === 'all-skip') {
977
+ sticky = 'skip';
978
+ }
979
+ else {
980
+ decision = ans;
981
+ }
982
+ }
983
+ if (sticky)
984
+ decision = sticky;
985
+ if (decision === 'overwrite') {
986
+ await fs.promises.writeFile(dest, data, 'utf8');
987
+ created.push(path.posix.normalize(dest));
988
+ }
989
+ else if (decision === 'example') {
990
+ const ex = `${dest}.example`;
991
+ await writeIfAbsent(ex, data);
992
+ examples.push(path.posix.normalize(ex));
993
+ }
994
+ else {
995
+ skipped.push(path.posix.normalize(dest));
953
996
  }
954
997
  }
955
998
  };
956
- const runInit = async (root, template = 'minimal', opts) => {
999
+ const runInit = async (root, template = 'default', opts) => {
957
1000
  const created = [];
958
1001
  const skipped = [];
959
1002
  const examples = [];
960
1003
  const merged = [];
1004
+ const optAll = opts ?? {};
961
1005
  const templatesBase = resolveTemplatesBase();
962
- const srcBase = path.resolve(templatesBase, template);
1006
+ // Resolve template source: named template (default/minimal/full) or filesystem path
1007
+ const templateIsPath = fs.existsSync(template) && (await fs.promises.stat(template)).isDirectory();
1008
+ const srcBase = templateIsPath
1009
+ ? path.resolve(template)
1010
+ : path.resolve(templatesBase, template);
963
1011
  const projectBase = path.resolve(templatesBase, 'project');
964
1012
  if (!fs.existsSync(srcBase)) {
965
- throw new Error(`Template "${template}" not found under ${toPosix(templatesBase)}.`);
1013
+ throw new Error(`Template "${template}" not found (path or name). Tried: ${toPosix(srcBase)}.`);
966
1014
  }
967
1015
  // 1) Copy shared boilerplate (project) first (idempotent)
968
1016
  if (fs.existsSync(projectBase)) {
969
- await copyDirIdempotent(projectBase, root, created, skipped, examples);
1017
+ const rl = optAll.yes
1018
+ ? undefined
1019
+ : promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
1020
+ let policy;
1021
+ const c = optAll.conflict;
1022
+ if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
1023
+ policy = c;
1024
+ else
1025
+ policy = optAll.yes ? 'example' : 'ask';
1026
+ const copyOpts = rl
1027
+ ? { conflict: policy, rl }
1028
+ : { conflict: policy };
1029
+ await copyDirWithConflicts(projectBase, root, created, skipped, examples, copyOpts);
1030
+ if (rl)
1031
+ rl.close();
970
1032
  }
971
1033
  // 2) Copy selected template
972
- await copyDirIdempotent(srcBase, root, created, skipped, examples);
1034
+ {
1035
+ const rl = optAll.yes
1036
+ ? undefined
1037
+ : promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
1038
+ let policy;
1039
+ const c = optAll.conflict;
1040
+ if (c === 'overwrite' || c === 'example' || c === 'skip' || c === 'ask')
1041
+ policy = c;
1042
+ else
1043
+ policy = optAll.yes ? 'example' : 'ask';
1044
+ const copyOpts = rl
1045
+ ? { conflict: policy, rl }
1046
+ : { conflict: policy };
1047
+ await copyDirWithConflicts(srcBase, root, created, skipped, examples, copyOpts);
1048
+ if (rl)
1049
+ rl.close();
1050
+ }
973
1051
  // 2.5) Convert template 'gitignore' into real '.gitignore'
974
1052
  // NPM often excludes '.gitignore' from published packages; shipping 'gitignore'
975
1053
  // and converting here ensures downstream projects get a proper .gitignore.
@@ -1020,55 +1098,59 @@ const runInit = async (root, template = 'minimal', opts) => {
1020
1098
  else
1021
1099
  skipped.push(path.posix.normalize(s.path));
1022
1100
  }
1023
- // 3) package.json presence (guard or create with --init)
1101
+ // 3) package.json presence (create when missing)
1024
1102
  const pkgPath = path.join(root, 'package.json');
1025
1103
  let pkg = await readJson(pkgPath);
1026
1104
  if (!pkg) {
1027
- const shouldInit = !!(opts && opts.init);
1028
- if (shouldInit) {
1029
- const name = toPosix(root).split('/').pop() ?? 'smoz-app';
1030
- pkg = {
1031
- name,
1032
- private: true,
1033
- type: 'module',
1034
- version: '0.0.0',
1035
- scripts: {},
1036
- };
1037
- // Avoid optional chain to satisfy no-unnecessary-condition; normalize to boolean.
1038
- const dryRunCreate = !!opts.dryRun;
1039
- if (!dryRunCreate)
1040
- await writeJson(pkgPath, pkg);
1041
- created.push(path.posix.normalize(pkgPath));
1042
- }
1043
- else {
1044
- throw new Error('No package.json found. Run "npm init -y" (or re-run with --init) and then "smoz init" again.');
1045
- }
1046
- }
1047
- // 4) Merge manifest (deps/devDeps/scripts) additively
1048
- const manifestPath = path.resolve(templatesBase, '.manifests', `package.${template}.json`);
1049
- const manifest = await readJson(manifestPath);
1105
+ const name = toPosix(root).split('/').pop() ?? 'smoz-app';
1106
+ pkg = {
1107
+ name,
1108
+ private: true,
1109
+ type: 'module',
1110
+ version: '0.0.0',
1111
+ scripts: {},
1112
+ };
1113
+ const dryRunCreate = Boolean(optAll.dryRun);
1114
+ if (!dryRunCreate)
1115
+ await writeJson(pkgPath, pkg);
1116
+ created.push(path.posix.normalize(pkgPath));
1117
+ } // 4) Merge manifest (deps/devDeps/scripts) additively
1118
+ // Prefer a real package.json in the template; fallback to legacy manifests if absent.
1119
+ const templatePkgPath = path.resolve(srcBase, 'package.json');
1120
+ const manifest = fs.existsSync(templatePkgPath)
1121
+ ? await readJson(templatePkgPath)
1122
+ : await readJson(path.resolve(templatesBase, '.manifests', `package.${template}.json`));
1050
1123
  if (manifest) {
1051
1124
  const before = JSON.stringify(pkg);
1052
1125
  const added = mergeAdditive(pkg, manifest);
1053
1126
  merged.push(...added);
1054
- const dryRun = !!(opts && opts.dryRun);
1127
+ const dryRun = Boolean(optAll.dryRun);
1055
1128
  if (!dryRun && before !== JSON.stringify(pkg)) {
1056
1129
  await writeJson(pkgPath, pkg);
1057
1130
  }
1058
1131
  }
1059
1132
  // 5) Optional install
1060
1133
  let installed = 'skipped';
1061
- const installOpt = opts ? opts.install : false;
1134
+ // Install policy:
1135
+ // -y implies auto install unless --no-install
1136
+ const installOpt = optAll.install ?? false;
1137
+ const impliedAuto = optAll.yes === true && optAll.noInstall !== true && installOpt !== false;
1062
1138
  // Derive package manager to use (explicit string or detected when true),
1063
1139
  // then set installed based on presence of a PM.
1064
1140
  const hasInstallString = typeof installOpt === 'string' && installOpt.trim() !== '';
1065
1141
  const pm = hasInstallString
1066
1142
  ? installOpt
1067
- : installOpt === true
1143
+ : installOpt === true || impliedAuto
1068
1144
  ? detectPm(root)
1069
1145
  : undefined;
1070
1146
  installed = pm ? runInstall(root, pm) : installed;
1071
- return { created, skipped, examples, merged, installed };
1147
+ return {
1148
+ created,
1149
+ skipped,
1150
+ examples,
1151
+ merged,
1152
+ installed,
1153
+ };
1072
1154
  };
1073
1155
 
1074
1156
  /**
@@ -1089,6 +1171,22 @@ const readPkg = (root) => {
1089
1171
  return {};
1090
1172
  }
1091
1173
  };
1174
+ const readSmozConfig = (root) => {
1175
+ try {
1176
+ const p = path.join(root, 'smoz.config.json');
1177
+ if (!fs.existsSync(p))
1178
+ return {};
1179
+ const raw = fs.readFileSync(p, 'utf8');
1180
+ const parsed = JSON.parse(raw);
1181
+ if (parsed && typeof parsed === 'object') {
1182
+ return parsed;
1183
+ }
1184
+ return {};
1185
+ }
1186
+ catch {
1187
+ return {};
1188
+ }
1189
+ };
1092
1190
  const detectPackageManager = () => {
1093
1191
  const ua = process.env.npm_config_user_agent ?? '';
1094
1192
  if (ua.includes('pnpm'))
@@ -1134,7 +1232,8 @@ const main = () => {
1134
1232
  program
1135
1233
  .name('smoz')
1136
1234
  .description('SMOZ CLI')
1137
- .version(pkg.version ?? '0.0.0');
1235
+ // Add -v alias for version in addition to default --version behavior
1236
+ .version(pkg.version ?? '0.0.0', '-v, --version', 'output the version');
1138
1237
  program
1139
1238
  .command('add')
1140
1239
  .argument('<spec>', 'Add function: HTTP <eventType>/<segments...>/<method> or non-HTTP <eventType>/<segments...>')
@@ -1155,16 +1254,46 @@ const main = () => {
1155
1254
  });
1156
1255
  program
1157
1256
  .command('init')
1158
- .description('Scaffold a new SMOZ app from packaged templates (default: minimal)')
1159
- .option('--template <name>', 'Template name (minimal|full)', 'minimal')
1160
- .option('--init', 'Create a minimal package.json if missing')
1257
+ .description('Scaffold a new SMOZ app from packaged templates (default: default)')
1258
+ .option('-t, --template <nameOrPath>', 'Template name or directory path', 'default')
1161
1259
  .option('-i, --install [pm]', 'Install dependencies (optionally specify pm: npm|pnpm|yarn|bun)')
1162
- .option('--yes', 'Skip prompts (non-interactive)', false)
1260
+ .option('--no-install', 'Skip dependency installation (overrides -y)', false)
1261
+ .option('-y, --yes', 'Skip prompts (non-interactive)', false)
1163
1262
  .option('--dry-run', 'Show planned actions without writing', false)
1164
1263
  .action(async (opts) => {
1165
1264
  try {
1166
- const tpl = typeof opts.template === 'string' ? opts.template : 'minimal';
1167
- const { created, skipped, examples, merged, installed } = await runInit(root, tpl, opts);
1265
+ const cfg = readSmozConfig(root).cliDefaults?.init ?? {};
1266
+ // Resolve template: CLI > config > default
1267
+ const tpl = typeof opts.template === 'string'
1268
+ ? opts.template
1269
+ : typeof cfg.template === 'string'
1270
+ ? cfg.template
1271
+ : 'default';
1272
+ // Resolve install behavior: CLI > --no-install > config
1273
+ let install = opts.install;
1274
+ if (opts.noInstall === true) {
1275
+ install = false;
1276
+ }
1277
+ else if (install === undefined) {
1278
+ const d = cfg.install;
1279
+ install =
1280
+ d === 'auto'
1281
+ ? true
1282
+ : d === 'none'
1283
+ ? false
1284
+ : typeof d === 'string'
1285
+ ? d
1286
+ : undefined;
1287
+ }
1288
+ const conflict = typeof opts.conflict === 'string' ? opts.conflict : cfg.onConflict;
1289
+ const { created, skipped, examples, merged, installed } = await runInit(root, tpl, {
1290
+ // Include only when defined to satisfy exactOptionalPropertyTypes
1291
+ ...(install !== undefined ? { install } : {}),
1292
+ ...(typeof conflict === 'string' ? { conflict } : {}),
1293
+ yes: opts.yes === true,
1294
+ noInstall: opts.noInstall === true,
1295
+ dryRun: opts.dryRun === true,
1296
+ });
1168
1297
  console.log([
1169
1298
  created.length
1170
1299
  ? `Created:\n - ${created.join('\n - ')}`
@@ -1220,14 +1349,17 @@ const main = () => {
1220
1349
  .option('-v, --verbose', 'Verbose logging', false)
1221
1350
  .action(async (opts) => {
1222
1351
  try {
1352
+ const cfg = readSmozConfig(root).cliDefaults?.dev ?? {};
1353
+ // Resolve local mode default from config if not provided
1354
+ const localResolved = typeof opts.local === 'string'
1355
+ ? opts.local
1356
+ : opts.local === false
1357
+ ? false
1358
+ : (cfg.local ?? 'inline');
1223
1359
  await runDev(root, {
1224
1360
  register: opts.register !== false,
1225
1361
  openapi: opts.openapi !== false,
1226
- local: typeof opts.local === 'string'
1227
- ? opts.local
1228
- : opts.local === false
1229
- ? false
1230
- : 'inline',
1362
+ local: localResolved,
1231
1363
  ...(typeof opts.stage === 'string' ? { stage: opts.stage } : {}),
1232
1364
  port: opts.port ?? 0,
1233
1365
  verbose: !!opts.verbose,
package/package.json CHANGED
@@ -182,8 +182,8 @@
182
182
  "test": "vitest run",
183
183
  "typecheck": "tsx src/cli/index.ts register && tsc -p tsconfig.json --noEmit",
184
184
  "templates:typecheck": "tsx scripts/templates-typecheck.ts",
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"
185
+ "templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" && eslint --fix templates/default/eslint.config.ts"
186
186
  },
187
187
  "type": "module",
188
- "version": "0.1.8"
188
+ "version": "0.2.0"
189
189
  }
@@ -1,50 +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: ['PROFILE', '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
- );
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: ['PROFILE', '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
+ );
@@ -1,45 +1,45 @@
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 (templates/minimal/types/registers.d.ts) so
12
- * TypeScript can typecheck without artifacts.
13
- * - To ensure side effects still run (endpoint registration) and to satisfy
14
- * noUncheckedSideEffectImports, import the register module as a namespace and
15
- * reference it via `void`.
16
- * - In real apps, `smoz init` seeds placeholders and `smoz register` rewrites
17
- * app/generated/register.*.ts at author time.
18
- */
19
- import * as __register_openapi from '@/app/generated/register.openapi';
20
- void __register_openapi;
21
- console.log('Generating OpenAPI document...');
22
-
23
- const paths = app.buildAllOpenApiPaths();
24
- export const doc = createDocument({
25
- openapi: '3.1.0',
26
- servers: [{ description: 'Dev', url: 'http://localhost' }],
27
- info: {
28
- title: process.env.npm_package_name ?? 'smoz-app',
29
- version: process.env.npm_package_version ?? '0.0.0',
30
- },
31
- paths,
32
- });
33
-
34
- const pkgDir = packageDirectorySync();
35
- if (!pkgDir) {
36
- throw new Error('Could not resolve package root directory');
37
- }
38
- const outDir = path.join(pkgDir, 'app', 'generated');
39
- fs.ensureDirSync(outDir);
40
- fs.writeFileSync(
41
- path.join(outDir, 'openapi.json'),
42
- JSON.stringify(doc, null, 2),
43
- );
44
-
45
- console.log('Done!');
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 (templates/minimal/types/registers.d.ts) so
12
+ * TypeScript can typecheck without artifacts.
13
+ * - To ensure side effects still run (endpoint registration) and to satisfy
14
+ * noUncheckedSideEffectImports, import the register module as a namespace and
15
+ * reference it via `void`.
16
+ * - In real apps, `smoz init` seeds placeholders and `smoz register` rewrites
17
+ * app/generated/register.*.ts at author time.
18
+ */
19
+ import * as __register_openapi from '@/app/generated/register.openapi';
20
+ void __register_openapi;
21
+ console.log('Generating OpenAPI document...');
22
+
23
+ const paths = app.buildAllOpenApiPaths();
24
+ export const doc = createDocument({
25
+ openapi: '3.1.0',
26
+ servers: [{ description: 'Dev', url: 'http://localhost' }],
27
+ info: {
28
+ title: process.env.npm_package_name ?? 'smoz-app',
29
+ version: process.env.npm_package_version ?? '0.0.0',
30
+ },
31
+ paths,
32
+ });
33
+
34
+ const pkgDir = packageDirectorySync();
35
+ if (!pkgDir) {
36
+ throw new Error('Could not resolve package root directory');
37
+ }
38
+ const outDir = path.join(pkgDir, 'app', 'generated');
39
+ fs.ensureDirSync(outDir);
40
+ fs.writeFileSync(
41
+ path.join(outDir, 'openapi.json'),
42
+ JSON.stringify(doc, null, 2),
43
+ );
44
+
45
+ console.log('Done!');
@@ -1,18 +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
- });
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
+ });
@@ -1,28 +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;
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;