@karmaniverous/smoz 0.2.0 → 0.2.2
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 +28 -4
- package/dist/cli/index.cjs +216 -143
- package/package.json +5 -9
- package/templates/default/app/config/openapi.ts +1 -1
- package/templates/default/package.json +4 -1
- package/templates/default/tsconfig.downstream.json +18 -0
- package/templates/default/tsconfig.eslint.json +1 -11
- package/templates/default/tsconfig.json +17 -7
package/README.md
CHANGED
|
@@ -18,17 +18,41 @@ SMOZ is a small, pragmatic toolkit for authoring AWS Lambda handlers with [Middy
|
|
|
18
18
|
- HTTP middleware with validation, shaping, errors, CORS, negotiation, and HEAD
|
|
19
19
|
- Non‑HTTP flows stay lean (no middleware overhead)
|
|
20
20
|
|
|
21
|
-
## Quick
|
|
21
|
+
## Quick start (from zero)
|
|
22
|
+
|
|
23
|
+
From an empty directory:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @karmaniverous/smoz init -i
|
|
27
|
+
npx smoz dev -p 3000
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- The first command scaffolds a new app and installs dependencies (including a local `smoz` bin).
|
|
31
|
+
- The second command starts the inline local backend and keeps registers + OpenAPI fresh.
|
|
32
|
+
- Open http://localhost:3000/openapi in your browser.
|
|
22
33
|
|
|
34
|
+
Prefer `serverless‑offline`?
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx smoz dev -l offline -p 3000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Add your first endpoint (avoid clashing with the template’s hello):
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx smoz add rest/foo/get
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quick links
|
|
47
|
+
|
|
23
48
|
- [Overview](https://docs.karmanivero.us/smoz/documents/Overview.html)
|
|
24
49
|
- [Why smoz?](https://docs.karmanivero.us/smoz/documents/Why_smoz_.html)
|
|
25
50
|
- [Getting started](https://docs.karmanivero.us/smoz/documents/Getting_started.html)
|
|
26
51
|
- [10-minute tour](https://docs.karmanivero.us/smoz/documents/10%E2%80%91minute_tour.html)
|
|
27
|
-
- [HTTP
|
|
52
|
+
- [HTTP Middleware](https://docs.karmanivero.us/smoz/documents/HTTP_middleware.html)
|
|
28
53
|
- [Recipes](https://docs.karmanivero.us/smoz/documents/Recipes.html)
|
|
29
54
|
- [SQS function](https://docs.karmanivero.us/smoz/documents/Recipes.SQS_function.html)
|
|
30
|
-
- [Contexts + Cognito authorizer](https://docs.karmanivero.us/smoz/documents/Recipes.Contexts_+_Cognito_authorizer.html)
|
|
31
|
-
- [Custom middleware insertion](https://docs.karmanivero.us/smoz/documents/Recipes.Custom_middleware_insertion.html)
|
|
55
|
+
- [Contexts + Cognito authorizer](https://docs.karmanivero.us/smoz/documents/Recipes.Contexts_+_Cognito_authorizer.html) - [Custom middleware insertion](https://docs.karmanivero.us/smoz/documents/Recipes.Custom_middleware_insertion.html)
|
|
32
56
|
- [Per‑function env](<https://docs.karmanivero.us/smoz/documents/Recipes.Per%E2%80%91function_env_(fnEnvKeys).html>)
|
|
33
57
|
- [Observability](<https://docs.karmanivero.us/smoz/documents/Recipes.Observability_(requestId_header).html>)
|
|
34
58
|
- [Troubleshooting](https://docs.karmanivero.us/smoz/documents/Recipes.Troubleshooting.html)
|
package/dist/cli/index.cjs
CHANGED
|
@@ -792,16 +792,6 @@ const launchInline = async (root, opts) => {
|
|
|
792
792
|
return { close, restart };
|
|
793
793
|
};
|
|
794
794
|
|
|
795
|
-
/**
|
|
796
|
-
* smoz init
|
|
797
|
-
*
|
|
798
|
-
* Scaffolds a new project from packaged templates.
|
|
799
|
-
* - Copies ./templates/<template>/ into the target root (default: default)
|
|
800
|
-
* - Seeds app/generated/register.*.ts (empty modules) if missing * - Idempotent: copy-if-absent; if a file exists, writes <name>.example alongside
|
|
801
|
-
* - Additive merge of template manifest (deps/devDeps/scripts) into package.json
|
|
802
|
-
* - Optional dependency installation via --install[=<pm>]
|
|
803
|
-
*/
|
|
804
|
-
const toPosix = (p) => p.split(path.sep).join('/');
|
|
805
795
|
const writeIfAbsent = async (outFile, content) => {
|
|
806
796
|
if (fs.existsSync(outFile))
|
|
807
797
|
return { created: false };
|
|
@@ -809,25 +799,6 @@ const writeIfAbsent = async (outFile, content) => {
|
|
|
809
799
|
await fs.promises.writeFile(outFile, content, 'utf8');
|
|
810
800
|
return { created: true };
|
|
811
801
|
};
|
|
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
|
-
};
|
|
831
802
|
const walk = async (dir, out = []) => {
|
|
832
803
|
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
833
804
|
for (const ent of entries) {
|
|
@@ -841,15 +812,6 @@ const walk = async (dir, out = []) => {
|
|
|
841
812
|
}
|
|
842
813
|
return out;
|
|
843
814
|
};
|
|
844
|
-
const resolveTemplatesBase = () => {
|
|
845
|
-
// Resolve the templates folder from the CLI package install root,
|
|
846
|
-
// not the caller’s project root. This makes --template <name> work
|
|
847
|
-
// consistently whether smoz is run from a consuming app or this repo.
|
|
848
|
-
// Anchor discovery to this module’s directory.
|
|
849
|
-
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))));
|
|
850
|
-
const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd(); // conservative fallback
|
|
851
|
-
return path.resolve(pkgRoot, 'templates');
|
|
852
|
-
};
|
|
853
815
|
const readJson = async (file) => {
|
|
854
816
|
try {
|
|
855
817
|
const data = await fs.promises.readFile(file, 'utf8');
|
|
@@ -863,6 +825,83 @@ const writeJson = async (file, obj) => {
|
|
|
863
825
|
await fs.promises.mkdir(path.dirname(file), { recursive: true });
|
|
864
826
|
await fs.promises.writeFile(file, JSON.stringify(obj, null, 2), 'utf8');
|
|
865
827
|
};
|
|
828
|
+
|
|
829
|
+
const askConflict = async (rl, filePath) => {
|
|
830
|
+
const q = `File exists: ${filePath}\n` +
|
|
831
|
+
`Choose: [o]verwrite, [e]xample, [s]kip, ` +
|
|
832
|
+
`[O]verwrite all, [E]xample all, [S]kip all: `;
|
|
833
|
+
const ans = (await rl.question(q)).trim();
|
|
834
|
+
if (/^o$/.test(ans))
|
|
835
|
+
return 'overwrite';
|
|
836
|
+
if (/^e$/.test(ans))
|
|
837
|
+
return 'example';
|
|
838
|
+
if (/^s$/.test(ans))
|
|
839
|
+
return 'skip';
|
|
840
|
+
if (/^O$/.test(ans))
|
|
841
|
+
return 'all-overwrite';
|
|
842
|
+
if (/^E$/.test(ans))
|
|
843
|
+
return 'all-example';
|
|
844
|
+
if (/^S$/.test(ans))
|
|
845
|
+
return 'all-skip';
|
|
846
|
+
return 'example';
|
|
847
|
+
};
|
|
848
|
+
const copyDirWithConflicts = async (srcDir, dstRoot, created, skipped, examples, opts) => {
|
|
849
|
+
const files = await walk(srcDir);
|
|
850
|
+
let sticky;
|
|
851
|
+
for (const abs of files) {
|
|
852
|
+
const rel = path.relative(srcDir, abs);
|
|
853
|
+
if (opts.exclude && opts.exclude(rel)) {
|
|
854
|
+
// Skip dev-only or excluded files silently.
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
const dest = path.resolve(dstRoot, rel);
|
|
858
|
+
const data = await fs.promises.readFile(abs, 'utf8');
|
|
859
|
+
if (!fs.existsSync(dest)) {
|
|
860
|
+
const { created: c } = await writeIfAbsent(dest, data);
|
|
861
|
+
if (c)
|
|
862
|
+
created.push(path.posix.normalize(dest));
|
|
863
|
+
else
|
|
864
|
+
skipped.push(path.posix.normalize(dest));
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
// Conflict flow
|
|
868
|
+
let decision = opts.conflict === 'ask' ? 'example' : opts.conflict;
|
|
869
|
+
if (opts.conflict === 'ask' && opts.rl && !sticky) {
|
|
870
|
+
const ans = await askConflict(opts.rl, path.posix.normalize(dest));
|
|
871
|
+
if (ans === 'all-overwrite') {
|
|
872
|
+
sticky = 'overwrite';
|
|
873
|
+
}
|
|
874
|
+
else if (ans === 'all-example') {
|
|
875
|
+
sticky = 'example';
|
|
876
|
+
}
|
|
877
|
+
else if (ans === 'all-skip') {
|
|
878
|
+
sticky = 'skip';
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
decision = ans;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (sticky)
|
|
885
|
+
decision = sticky;
|
|
886
|
+
if (decision === 'overwrite') {
|
|
887
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
888
|
+
await fs.promises.writeFile(dest, data, 'utf8');
|
|
889
|
+
created.push(path.posix.normalize(dest));
|
|
890
|
+
}
|
|
891
|
+
else if (decision === 'example') {
|
|
892
|
+
const ex = `${dest}.example`;
|
|
893
|
+
const { created: c } = await writeIfAbsent(ex, data);
|
|
894
|
+
if (c)
|
|
895
|
+
examples.push(path.posix.normalize(ex));
|
|
896
|
+
else
|
|
897
|
+
skipped.push(path.posix.normalize(ex));
|
|
898
|
+
}
|
|
899
|
+
else {
|
|
900
|
+
skipped.push(path.posix.normalize(dest));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
866
905
|
const detectPm = (root) => {
|
|
867
906
|
if (fs.existsSync(path.join(root, 'pnpm-lock.yaml')))
|
|
868
907
|
return 'pnpm';
|
|
@@ -889,7 +928,6 @@ const runInstall = (root, pm) => {
|
|
|
889
928
|
const known = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' || pm === 'npm';
|
|
890
929
|
if (!known)
|
|
891
930
|
return 'unknown-pm';
|
|
892
|
-
// Spawn with explicit args; avoid tuple inference that widens types.
|
|
893
931
|
const res = node_child_process.spawnSync(pm, ['install'], {
|
|
894
932
|
stdio: 'inherit',
|
|
895
933
|
cwd: root,
|
|
@@ -907,10 +945,10 @@ const runInstall = (root, pm) => {
|
|
|
907
945
|
}
|
|
908
946
|
return 'failed';
|
|
909
947
|
};
|
|
948
|
+
|
|
910
949
|
const mergeAdditive = (target, source) => {
|
|
911
950
|
const merged = [];
|
|
912
951
|
const mergeKey = (key) => {
|
|
913
|
-
// Allow possibly-undefined shapes to satisfy lint (no-unnecessary-condition).
|
|
914
952
|
const src = source[key] ?? {};
|
|
915
953
|
const dst = target[key] ?? {};
|
|
916
954
|
const out = { ...dst };
|
|
@@ -948,53 +986,81 @@ const mergeAdditive = (target, source) => {
|
|
|
948
986
|
target.scripts = scriptsOut;
|
|
949
987
|
return merged;
|
|
950
988
|
};
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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));
|
|
989
|
+
/**
|
|
990
|
+
* Ensure a runtime dependency on @karmaniverous/smoz is present in the
|
|
991
|
+
* target manifest, using a caret version derived from the toolkit package.
|
|
992
|
+
* Returns the merge descriptor string when added, else undefined.
|
|
993
|
+
*/
|
|
994
|
+
const ensureToolkitDependency = async (targetPkg, templatesBase) => {
|
|
995
|
+
try {
|
|
996
|
+
const toolkitRoot = path.dirname(templatesBase);
|
|
997
|
+
const toolkitPkg = await readJson(path.join(toolkitRoot, 'package.json'));
|
|
998
|
+
const verRaw = toolkitPkg?.version?.trim();
|
|
999
|
+
const depVersion = verRaw ? `^${verRaw}` : '^0.0.0';
|
|
1000
|
+
const deps = targetPkg.dependencies ?? {};
|
|
1001
|
+
if (!deps['@karmaniverous/smoz']) {
|
|
1002
|
+
targetPkg.dependencies ??=
|
|
1003
|
+
{};
|
|
1004
|
+
targetPkg.dependencies['@karmaniverous/smoz'] = depVersion;
|
|
1005
|
+
return `dependencies:@karmaniverous/smoz@${depVersion}`;
|
|
996
1006
|
}
|
|
997
1007
|
}
|
|
1008
|
+
catch {
|
|
1009
|
+
// best-effort; ignore
|
|
1010
|
+
}
|
|
1011
|
+
return undefined;
|
|
1012
|
+
};
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
+ * Resolve the packaged templates root from the CLI install location,
|
|
1016
|
+
* not the caller's project root. This makes -t <name> work both from
|
|
1017
|
+
* a consuming app and from this repository.
|
|
1018
|
+
+ */
|
|
1019
|
+
const resolveTemplatesBase = () => {
|
|
1020
|
+
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))));
|
|
1021
|
+
const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
1022
|
+
return path.resolve(pkgRoot, 'templates');
|
|
1023
|
+
};
|
|
1024
|
+
const toPosix = (p) =>
|
|
1025
|
+
// local helper for path presentation (not used for FS operations)
|
|
1026
|
+
p.replace(/\\/g, '/');
|
|
1027
|
+
|
|
1028
|
+
const seedRegisterPlaceholders = async (root) => {
|
|
1029
|
+
const genDir = path.join(root, 'app', 'generated');
|
|
1030
|
+
const seeds = [
|
|
1031
|
+
{
|
|
1032
|
+
path: path.join(genDir, 'register.functions.ts'),
|
|
1033
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
path: path.join(genDir, 'register.openapi.ts'),
|
|
1037
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
path: path.join(genDir, 'register.serverless.ts'),
|
|
1041
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1042
|
+
},
|
|
1043
|
+
];
|
|
1044
|
+
const created = [];
|
|
1045
|
+
const skipped = [];
|
|
1046
|
+
for (const s of seeds) {
|
|
1047
|
+
const { created: c } = await writeIfAbsent(s.path, s.content);
|
|
1048
|
+
(c ? created : skipped).push(path.posix.normalize(s.path));
|
|
1049
|
+
}
|
|
1050
|
+
return { created, skipped };
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const toPosixSep = (p) => p.split(path.sep).join('/');
|
|
1054
|
+
// Exclude dev-only type stubs (not meant for downstream apps).
|
|
1055
|
+
const excludeDev = (rel) => {
|
|
1056
|
+
const posixRel = rel.replace(/\\/g, '/');
|
|
1057
|
+
return /\/types\/[^/]*\.dev\.d\.ts$/i.test(posixRel);
|
|
1058
|
+
};
|
|
1059
|
+
const excludeTemplate = (rel) => {
|
|
1060
|
+
const posixRel = rel.replace(/\\/g, '/');
|
|
1061
|
+
if (posixRel === 'tsconfig.json')
|
|
1062
|
+
return true; // skip dev tsconfig from template
|
|
1063
|
+
return excludeDev(rel);
|
|
998
1064
|
};
|
|
999
1065
|
const runInit = async (root, template = 'default', opts) => {
|
|
1000
1066
|
const created = [];
|
|
@@ -1003,7 +1069,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1003
1069
|
const merged = [];
|
|
1004
1070
|
const optAll = opts ?? {};
|
|
1005
1071
|
const templatesBase = resolveTemplatesBase();
|
|
1006
|
-
// Resolve template source: named template
|
|
1072
|
+
// Resolve template source: named template or filesystem path
|
|
1007
1073
|
const templateIsPath = fs.existsSync(template) && (await fs.promises.stat(template)).isDirectory();
|
|
1008
1074
|
const srcBase = templateIsPath
|
|
1009
1075
|
? path.resolve(template)
|
|
@@ -1012,9 +1078,9 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1012
1078
|
if (!fs.existsSync(srcBase)) {
|
|
1013
1079
|
throw new Error(`Template "${template}" not found (path or name). Tried: ${toPosix(srcBase)}.`);
|
|
1014
1080
|
}
|
|
1015
|
-
// 1) Copy shared
|
|
1081
|
+
// 1) Copy shared project boilerplate
|
|
1016
1082
|
if (fs.existsSync(projectBase)) {
|
|
1017
|
-
const rl = optAll.yes
|
|
1083
|
+
const rl = optAll.yes === true
|
|
1018
1084
|
? undefined
|
|
1019
1085
|
: promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
|
|
1020
1086
|
let policy;
|
|
@@ -1026,13 +1092,15 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1026
1092
|
const copyOpts = rl
|
|
1027
1093
|
? { conflict: policy, rl }
|
|
1028
1094
|
: { conflict: policy };
|
|
1029
|
-
await copyDirWithConflicts(projectBase, root, created, skipped, examples,
|
|
1030
|
-
|
|
1031
|
-
|
|
1095
|
+
await copyDirWithConflicts(projectBase, root, created, skipped, examples, {
|
|
1096
|
+
...copyOpts,
|
|
1097
|
+
exclude: excludeDev,
|
|
1098
|
+
});
|
|
1099
|
+
rl?.close();
|
|
1032
1100
|
}
|
|
1033
1101
|
// 2) Copy selected template
|
|
1034
1102
|
{
|
|
1035
|
-
const rl = optAll.yes
|
|
1103
|
+
const rl = optAll.yes === true
|
|
1036
1104
|
? undefined
|
|
1037
1105
|
: promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
|
|
1038
1106
|
let policy;
|
|
@@ -1044,13 +1112,13 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1044
1112
|
const copyOpts = rl
|
|
1045
1113
|
? { conflict: policy, rl }
|
|
1046
1114
|
: { conflict: policy };
|
|
1047
|
-
await copyDirWithConflicts(srcBase, root, created, skipped, examples,
|
|
1048
|
-
|
|
1049
|
-
|
|
1115
|
+
await copyDirWithConflicts(srcBase, root, created, skipped, examples, {
|
|
1116
|
+
...copyOpts,
|
|
1117
|
+
exclude: excludeTemplate,
|
|
1118
|
+
});
|
|
1119
|
+
rl?.close();
|
|
1050
1120
|
}
|
|
1051
|
-
// 2.5) Convert template 'gitignore' into real '.gitignore'
|
|
1052
|
-
// NPM often excludes '.gitignore' from published packages; shipping 'gitignore'
|
|
1053
|
-
// and converting here ensures downstream projects get a proper .gitignore.
|
|
1121
|
+
// 2.5) Convert template 'gitignore' into a real '.gitignore'
|
|
1054
1122
|
try {
|
|
1055
1123
|
const giSrc = path.join(root, 'gitignore');
|
|
1056
1124
|
const giDot = path.join(root, '.gitignore');
|
|
@@ -1060,8 +1128,6 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1060
1128
|
created.push(path.posix.normalize(giDot));
|
|
1061
1129
|
}
|
|
1062
1130
|
else {
|
|
1063
|
-
// Both exist: preserve the template as an example (if not already present),
|
|
1064
|
-
// then remove the extra 'gitignore' to avoid clutter.
|
|
1065
1131
|
const example = path.join(root, 'gitignore.example');
|
|
1066
1132
|
if (!fs.existsSync(example)) {
|
|
1067
1133
|
const data = await fs.promises.readFile(giSrc, 'utf8');
|
|
@@ -1073,36 +1139,43 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1073
1139
|
}
|
|
1074
1140
|
}
|
|
1075
1141
|
catch {
|
|
1076
|
-
// best-effort
|
|
1142
|
+
// best-effort
|
|
1077
1143
|
}
|
|
1078
|
-
//
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1144
|
+
// 2.6) Convert 'tsconfig.downstream.json' into 'tsconfig.json'
|
|
1145
|
+
try {
|
|
1146
|
+
const ds = path.join(root, 'tsconfig.downstream.json');
|
|
1147
|
+
const dst = path.join(root, 'tsconfig.json');
|
|
1148
|
+
if (fs.existsSync(ds)) {
|
|
1149
|
+
if (!fs.existsSync(dst)) {
|
|
1150
|
+
await fs.promises.rename(ds, dst);
|
|
1151
|
+
created.push(path.posix.normalize(dst));
|
|
1152
|
+
}
|
|
1153
|
+
else {
|
|
1154
|
+
// If a tsconfig already exists, write an example and remove the source
|
|
1155
|
+
const example = path.join(root, 'tsconfig.json.example');
|
|
1156
|
+
if (!fs.existsSync(example)) {
|
|
1157
|
+
const data = await fs.promises.readFile(ds, 'utf8');
|
|
1158
|
+
await fs.promises.writeFile(example, data, 'utf8');
|
|
1159
|
+
examples.push(path.posix.normalize(example));
|
|
1160
|
+
}
|
|
1161
|
+
await fs.promises.rm(ds, { force: true });
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
catch {
|
|
1166
|
+
// best-effort
|
|
1167
|
+
}
|
|
1168
|
+
// Seed app/generated/register.* placeholders
|
|
1169
|
+
{
|
|
1170
|
+
const res = await seedRegisterPlaceholders(root);
|
|
1171
|
+
created.push(...res.created);
|
|
1172
|
+
skipped.push(...res.skipped);
|
|
1100
1173
|
}
|
|
1101
1174
|
// 3) package.json presence (create when missing)
|
|
1102
1175
|
const pkgPath = path.join(root, 'package.json');
|
|
1103
1176
|
let pkg = await readJson(pkgPath);
|
|
1104
1177
|
if (!pkg) {
|
|
1105
|
-
const name =
|
|
1178
|
+
const name = toPosixSep(root).split('/').pop() ?? 'smoz-app';
|
|
1106
1179
|
pkg = {
|
|
1107
1180
|
name,
|
|
1108
1181
|
private: true,
|
|
@@ -1110,33 +1183,39 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1110
1183
|
version: '0.0.0',
|
|
1111
1184
|
scripts: {},
|
|
1112
1185
|
};
|
|
1113
|
-
|
|
1114
|
-
if (!dryRunCreate)
|
|
1186
|
+
if (!optAll.dryRun)
|
|
1115
1187
|
await writeJson(pkgPath, pkg);
|
|
1116
1188
|
created.push(path.posix.normalize(pkgPath));
|
|
1117
|
-
}
|
|
1118
|
-
//
|
|
1189
|
+
}
|
|
1190
|
+
// 4) Merge manifest additively (prefer template's embedded package.json)
|
|
1119
1191
|
const templatePkgPath = path.resolve(srcBase, 'package.json');
|
|
1120
1192
|
const manifest = fs.existsSync(templatePkgPath)
|
|
1121
1193
|
? await readJson(templatePkgPath)
|
|
1122
1194
|
: await readJson(path.resolve(templatesBase, '.manifests', `package.${template}.json`));
|
|
1195
|
+
let pkgChanged = false;
|
|
1123
1196
|
if (manifest) {
|
|
1124
|
-
const before = JSON.stringify(pkg);
|
|
1125
1197
|
const added = mergeAdditive(pkg, manifest);
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1198
|
+
if (added.length > 0) {
|
|
1199
|
+
pkgChanged = true;
|
|
1200
|
+
merged.push(...added);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
// 4.5) Ensure runtime dependency on @karmaniverous/smoz is present
|
|
1204
|
+
{
|
|
1205
|
+
const injected = await ensureToolkitDependency(pkg, templatesBase);
|
|
1206
|
+
if (injected) {
|
|
1207
|
+
merged.push(injected);
|
|
1208
|
+
pkgChanged = true;
|
|
1130
1209
|
}
|
|
1131
1210
|
}
|
|
1211
|
+
if (!optAll.dryRun && pkgChanged)
|
|
1212
|
+
await writeJson(pkgPath, pkg);
|
|
1132
1213
|
// 5) Optional install
|
|
1133
1214
|
let installed = 'skipped';
|
|
1134
|
-
//
|
|
1215
|
+
// Policy:
|
|
1135
1216
|
// -y implies auto install unless --no-install
|
|
1136
1217
|
const installOpt = optAll.install ?? false;
|
|
1137
1218
|
const impliedAuto = optAll.yes === true && optAll.noInstall !== true && installOpt !== false;
|
|
1138
|
-
// Derive package manager to use (explicit string or detected when true),
|
|
1139
|
-
// then set installed based on presence of a PM.
|
|
1140
1219
|
const hasInstallString = typeof installOpt === 'string' && installOpt.trim() !== '';
|
|
1141
1220
|
const pm = hasInstallString
|
|
1142
1221
|
? installOpt
|
|
@@ -1144,13 +1223,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1144
1223
|
? detectPm(root)
|
|
1145
1224
|
: undefined;
|
|
1146
1225
|
installed = pm ? runInstall(root, pm) : installed;
|
|
1147
|
-
return {
|
|
1148
|
-
created,
|
|
1149
|
-
skipped,
|
|
1150
|
-
examples,
|
|
1151
|
-
merged,
|
|
1152
|
-
installed,
|
|
1153
|
-
};
|
|
1226
|
+
return { created, skipped, examples, merged, installed };
|
|
1154
1227
|
};
|
|
1155
1228
|
|
|
1156
1229
|
/**
|
package/package.json
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"url": "https://github.com/karmaniverous/smoz/issues"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@karmaniverous/cached-axios": "^0.2.0",
|
|
17
16
|
"@middy/core": "^6.4.5",
|
|
18
17
|
"@middy/http-content-negotiation": "^6.4.5",
|
|
19
18
|
"@middy/http-cors": "^6.4.5",
|
|
@@ -24,11 +23,11 @@
|
|
|
24
23
|
"@middy/http-response-serializer": "^6.4.5",
|
|
25
24
|
"aws-lambda": "^1.0.7",
|
|
26
25
|
"chokidar": "^4.0.3",
|
|
27
|
-
"commander": "^14.0.
|
|
26
|
+
"commander": "^14.0.1",
|
|
28
27
|
"http-errors": "^2.0.0",
|
|
29
28
|
"package-directory": "^8.1.0",
|
|
30
29
|
"radash": "^12.1.1",
|
|
31
|
-
"zod": "^4.1.
|
|
30
|
+
"zod": "^4.1.8"
|
|
32
31
|
},
|
|
33
32
|
"description": "John Galt Services back end.",
|
|
34
33
|
"devDependencies": {
|
|
@@ -48,8 +47,7 @@
|
|
|
48
47
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
49
48
|
"fs-extra": "^11.3.1",
|
|
50
49
|
"knip": "^5.63.1",
|
|
51
|
-
"lefthook": "^1.
|
|
52
|
-
"orval": "^7.11.2",
|
|
50
|
+
"lefthook": "^1.13.0",
|
|
53
51
|
"pkg-dir": "^9.0.0",
|
|
54
52
|
"prettier": "^3.6.2",
|
|
55
53
|
"release-it": "^19.0.4",
|
|
@@ -167,7 +165,6 @@
|
|
|
167
165
|
"docs": "typedoc",
|
|
168
166
|
"domain:create": "serverless create_domain",
|
|
169
167
|
"domain:delete": "serverless delete_domain",
|
|
170
|
-
"generate": "cd services/activecampaign && orval",
|
|
171
168
|
"knip": "knip",
|
|
172
169
|
"lint": "eslint .",
|
|
173
170
|
"lint:fix": "eslint --fix .",
|
|
@@ -177,13 +174,12 @@
|
|
|
177
174
|
"release:pre": "dotenvx run -f .env.local -- release-it --no-git.requireBranch --github.prerelease --preRelease",
|
|
178
175
|
"remove": "serverless remove",
|
|
179
176
|
"smoz": "tsx src/cli/index.ts",
|
|
180
|
-
"stan:build": "rimraf stan.dist && rollup --config stan.rollup.config.ts --configPlugin @rollup/plugin-typescript && rimraf stan.dist",
|
|
181
177
|
"stan:docs": "typedoc --emit none",
|
|
182
178
|
"test": "vitest run",
|
|
183
179
|
"typecheck": "tsx src/cli/index.ts register && tsc -p tsconfig.json --noEmit",
|
|
184
180
|
"templates:typecheck": "tsx scripts/templates-typecheck.ts",
|
|
185
|
-
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\"
|
|
181
|
+
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" \"templates/default/eslint.config.ts\""
|
|
186
182
|
},
|
|
187
183
|
"type": "module",
|
|
188
|
-
"version": "0.2.
|
|
184
|
+
"version": "0.2.2"
|
|
189
185
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
|
|
3
3
|
import * as fs from 'fs-extra';
|
|
4
|
-
import { packageDirectorySync } from '
|
|
4
|
+
import { packageDirectorySync } from 'package-directory';
|
|
5
5
|
import { createDocument } from 'zod-openapi';
|
|
6
6
|
|
|
7
7
|
import { app } from '@/app/config/app.config';
|
|
@@ -5,10 +5,13 @@
|
|
|
5
5
|
"version": "0.0.0",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@middy/core": "^6.4.5",
|
|
8
|
-
"zod": "^4.1.6"
|
|
8
|
+
"zod": "^4.1.6",
|
|
9
|
+
"fs-extra": "^11.3.1",
|
|
10
|
+
"package-directory": "^8.1.0"
|
|
9
11
|
},
|
|
10
12
|
"devDependencies": {
|
|
11
13
|
"@serverless/typescript": "^4.18.2",
|
|
14
|
+
"@types/fs-extra": "^11.0.4",
|
|
12
15
|
"@types/node": "^22",
|
|
13
16
|
"eslint": "^9.35.0",
|
|
14
17
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".",
|
|
4
|
+
"outDir": ".tsbuild",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": ["*"]
|
|
7
|
+
},
|
|
8
|
+
"tsBuildInfoFile": ".tsbuild/tsconfig.tsbuildinfo"
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
".serverless/**",
|
|
12
|
+
"node_modules/**",
|
|
13
|
+
"app/generated/**",
|
|
14
|
+
"dist/**"
|
|
15
|
+
],
|
|
16
|
+
"extends": "./tsconfig.base.json",
|
|
17
|
+
"include": ["**/*", "**/*.json"]
|
|
18
|
+
}
|
|
@@ -3,14 +3,24 @@
|
|
|
3
3
|
"baseUrl": ".",
|
|
4
4
|
"outDir": ".tsbuild",
|
|
5
5
|
"paths": {
|
|
6
|
-
"@/*": ["*"]
|
|
6
|
+
"@/*": ["*"],
|
|
7
|
+
"@karmaniverous/smoz": [
|
|
8
|
+
"../../dist/index",
|
|
9
|
+
"../../dist/index.d.ts"
|
|
10
|
+
]
|
|
7
11
|
},
|
|
12
|
+
"typeRoots": [
|
|
13
|
+
"./types",
|
|
14
|
+
"../../node_modules/@types"
|
|
15
|
+
],
|
|
8
16
|
"tsBuildInfoFile": ".tsbuild/tsconfig.tsbuildinfo"
|
|
9
17
|
},
|
|
10
|
-
"exclude": [
|
|
18
|
+
"exclude": [
|
|
19
|
+
".serverless/**",
|
|
20
|
+
"node_modules/**",
|
|
21
|
+
"app/generated/**",
|
|
22
|
+
"dist/**"
|
|
23
|
+
],
|
|
11
24
|
"extends": "./tsconfig.base.json",
|
|
12
|
-
"include": [
|
|
13
|
-
|
|
14
|
-
"**/*.json"
|
|
15
|
-
]
|
|
16
|
-
}
|
|
25
|
+
"include": ["**/*", "**/*.json"]
|
|
26
|
+
}
|