@karmaniverous/smoz 0.2.0 → 0.2.1
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 +169 -141
- package/package.json +4 -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,79 @@ 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
|
+
const dest = path.resolve(dstRoot, rel);
|
|
854
|
+
const data = await fs.promises.readFile(abs, 'utf8');
|
|
855
|
+
if (!fs.existsSync(dest)) {
|
|
856
|
+
const { created: c } = await writeIfAbsent(dest, data);
|
|
857
|
+
if (c)
|
|
858
|
+
created.push(path.posix.normalize(dest));
|
|
859
|
+
else
|
|
860
|
+
skipped.push(path.posix.normalize(dest));
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
// Conflict flow
|
|
864
|
+
let decision = opts.conflict === 'ask' ? 'example' : opts.conflict;
|
|
865
|
+
if (opts.conflict === 'ask' && opts.rl && !sticky) {
|
|
866
|
+
const ans = await askConflict(opts.rl, path.posix.normalize(dest));
|
|
867
|
+
if (ans === 'all-overwrite') {
|
|
868
|
+
sticky = 'overwrite';
|
|
869
|
+
}
|
|
870
|
+
else if (ans === 'all-example') {
|
|
871
|
+
sticky = 'example';
|
|
872
|
+
}
|
|
873
|
+
else if (ans === 'all-skip') {
|
|
874
|
+
sticky = 'skip';
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
decision = ans;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (sticky)
|
|
881
|
+
decision = sticky;
|
|
882
|
+
if (decision === 'overwrite') {
|
|
883
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
884
|
+
await fs.promises.writeFile(dest, data, 'utf8');
|
|
885
|
+
created.push(path.posix.normalize(dest));
|
|
886
|
+
}
|
|
887
|
+
else if (decision === 'example') {
|
|
888
|
+
const ex = `${dest}.example`;
|
|
889
|
+
const { created: c } = await writeIfAbsent(ex, data);
|
|
890
|
+
if (c)
|
|
891
|
+
examples.push(path.posix.normalize(ex));
|
|
892
|
+
else
|
|
893
|
+
skipped.push(path.posix.normalize(ex));
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
skipped.push(path.posix.normalize(dest));
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
|
|
866
901
|
const detectPm = (root) => {
|
|
867
902
|
if (fs.existsSync(path.join(root, 'pnpm-lock.yaml')))
|
|
868
903
|
return 'pnpm';
|
|
@@ -889,7 +924,6 @@ const runInstall = (root, pm) => {
|
|
|
889
924
|
const known = pm === 'pnpm' || pm === 'yarn' || pm === 'bun' || pm === 'npm';
|
|
890
925
|
if (!known)
|
|
891
926
|
return 'unknown-pm';
|
|
892
|
-
// Spawn with explicit args; avoid tuple inference that widens types.
|
|
893
927
|
const res = node_child_process.spawnSync(pm, ['install'], {
|
|
894
928
|
stdio: 'inherit',
|
|
895
929
|
cwd: root,
|
|
@@ -907,10 +941,10 @@ const runInstall = (root, pm) => {
|
|
|
907
941
|
}
|
|
908
942
|
return 'failed';
|
|
909
943
|
};
|
|
944
|
+
|
|
910
945
|
const mergeAdditive = (target, source) => {
|
|
911
946
|
const merged = [];
|
|
912
947
|
const mergeKey = (key) => {
|
|
913
|
-
// Allow possibly-undefined shapes to satisfy lint (no-unnecessary-condition).
|
|
914
948
|
const src = source[key] ?? {};
|
|
915
949
|
const dst = target[key] ?? {};
|
|
916
950
|
const out = { ...dst };
|
|
@@ -948,54 +982,71 @@ const mergeAdditive = (target, source) => {
|
|
|
948
982
|
target.scripts = scriptsOut;
|
|
949
983
|
return merged;
|
|
950
984
|
};
|
|
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));
|
|
985
|
+
/**
|
|
986
|
+
* Ensure a runtime dependency on @karmaniverous/smoz is present in the
|
|
987
|
+
* target manifest, using a caret version derived from the toolkit package.
|
|
988
|
+
* Returns the merge descriptor string when added, else undefined.
|
|
989
|
+
*/
|
|
990
|
+
const ensureToolkitDependency = async (targetPkg, templatesBase) => {
|
|
991
|
+
try {
|
|
992
|
+
const toolkitRoot = path.dirname(templatesBase);
|
|
993
|
+
const toolkitPkg = await readJson(path.join(toolkitRoot, 'package.json'));
|
|
994
|
+
const verRaw = toolkitPkg?.version?.trim();
|
|
995
|
+
const depVersion = verRaw ? `^${verRaw}` : '^0.0.0';
|
|
996
|
+
const deps = targetPkg.dependencies ?? {};
|
|
997
|
+
if (!deps['@karmaniverous/smoz']) {
|
|
998
|
+
targetPkg.dependencies ??=
|
|
999
|
+
{};
|
|
1000
|
+
targetPkg.dependencies['@karmaniverous/smoz'] = depVersion;
|
|
1001
|
+
return `dependencies:@karmaniverous/smoz@${depVersion}`;
|
|
996
1002
|
}
|
|
997
1003
|
}
|
|
1004
|
+
catch {
|
|
1005
|
+
// best-effort; ignore
|
|
1006
|
+
}
|
|
1007
|
+
return undefined;
|
|
998
1008
|
};
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
+ * Resolve the packaged templates root from the CLI install location,
|
|
1012
|
+
* not the caller's project root. This makes -t <name> work both from
|
|
1013
|
+
* a consuming app and from this repository.
|
|
1014
|
+
+ */
|
|
1015
|
+
const resolveTemplatesBase = () => {
|
|
1016
|
+
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))));
|
|
1017
|
+
const pkgRoot = packageDirectory.packageDirectorySync({ cwd: here }) ?? process.cwd();
|
|
1018
|
+
return path.resolve(pkgRoot, 'templates');
|
|
1019
|
+
};
|
|
1020
|
+
const toPosix = (p) =>
|
|
1021
|
+
// local helper for path presentation (not used for FS operations)
|
|
1022
|
+
p.replace(/\\/g, '/');
|
|
1023
|
+
|
|
1024
|
+
const seedRegisterPlaceholders = async (root) => {
|
|
1025
|
+
const genDir = path.join(root, 'app', 'generated');
|
|
1026
|
+
const seeds = [
|
|
1027
|
+
{
|
|
1028
|
+
path: path.join(genDir, 'register.functions.ts'),
|
|
1029
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
path: path.join(genDir, 'register.openapi.ts'),
|
|
1033
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
path: path.join(genDir, 'register.serverless.ts'),
|
|
1037
|
+
content: '/* AUTO-GENERATED placeholder; will be rewritten by `smoz register` */\nexport {};\n',
|
|
1038
|
+
},
|
|
1039
|
+
];
|
|
1040
|
+
const created = [];
|
|
1041
|
+
const skipped = [];
|
|
1042
|
+
for (const s of seeds) {
|
|
1043
|
+
const { created: c } = await writeIfAbsent(s.path, s.content);
|
|
1044
|
+
(c ? created : skipped).push(path.posix.normalize(s.path));
|
|
1045
|
+
}
|
|
1046
|
+
return { created, skipped };
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
const toPosixSep = (p) => p.split(path.sep).join('/');
|
|
999
1050
|
const runInit = async (root, template = 'default', opts) => {
|
|
1000
1051
|
const created = [];
|
|
1001
1052
|
const skipped = [];
|
|
@@ -1003,7 +1054,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1003
1054
|
const merged = [];
|
|
1004
1055
|
const optAll = opts ?? {};
|
|
1005
1056
|
const templatesBase = resolveTemplatesBase();
|
|
1006
|
-
// Resolve template source: named template
|
|
1057
|
+
// Resolve template source: named template or filesystem path
|
|
1007
1058
|
const templateIsPath = fs.existsSync(template) && (await fs.promises.stat(template)).isDirectory();
|
|
1008
1059
|
const srcBase = templateIsPath
|
|
1009
1060
|
? path.resolve(template)
|
|
@@ -1012,9 +1063,9 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1012
1063
|
if (!fs.existsSync(srcBase)) {
|
|
1013
1064
|
throw new Error(`Template "${template}" not found (path or name). Tried: ${toPosix(srcBase)}.`);
|
|
1014
1065
|
}
|
|
1015
|
-
// 1) Copy shared
|
|
1066
|
+
// 1) Copy shared project boilerplate
|
|
1016
1067
|
if (fs.existsSync(projectBase)) {
|
|
1017
|
-
const rl = optAll.yes
|
|
1068
|
+
const rl = optAll.yes === true
|
|
1018
1069
|
? undefined
|
|
1019
1070
|
: promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
|
|
1020
1071
|
let policy;
|
|
@@ -1027,12 +1078,11 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1027
1078
|
? { conflict: policy, rl }
|
|
1028
1079
|
: { conflict: policy };
|
|
1029
1080
|
await copyDirWithConflicts(projectBase, root, created, skipped, examples, copyOpts);
|
|
1030
|
-
|
|
1031
|
-
rl.close();
|
|
1081
|
+
rl?.close();
|
|
1032
1082
|
}
|
|
1033
1083
|
// 2) Copy selected template
|
|
1034
1084
|
{
|
|
1035
|
-
const rl = optAll.yes
|
|
1085
|
+
const rl = optAll.yes === true
|
|
1036
1086
|
? undefined
|
|
1037
1087
|
: promises.createInterface({ input: node_process.stdin, output: node_process.stdout, terminal: true });
|
|
1038
1088
|
let policy;
|
|
@@ -1045,12 +1095,9 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1045
1095
|
? { conflict: policy, rl }
|
|
1046
1096
|
: { conflict: policy };
|
|
1047
1097
|
await copyDirWithConflicts(srcBase, root, created, skipped, examples, copyOpts);
|
|
1048
|
-
|
|
1049
|
-
rl.close();
|
|
1098
|
+
rl?.close();
|
|
1050
1099
|
}
|
|
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.
|
|
1100
|
+
// 2.5) Convert template 'gitignore' into a real '.gitignore'
|
|
1054
1101
|
try {
|
|
1055
1102
|
const giSrc = path.join(root, 'gitignore');
|
|
1056
1103
|
const giDot = path.join(root, '.gitignore');
|
|
@@ -1060,8 +1107,6 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1060
1107
|
created.push(path.posix.normalize(giDot));
|
|
1061
1108
|
}
|
|
1062
1109
|
else {
|
|
1063
|
-
// Both exist: preserve the template as an example (if not already present),
|
|
1064
|
-
// then remove the extra 'gitignore' to avoid clutter.
|
|
1065
1110
|
const example = path.join(root, 'gitignore.example');
|
|
1066
1111
|
if (!fs.existsSync(example)) {
|
|
1067
1112
|
const data = await fs.promises.readFile(giSrc, 'utf8');
|
|
@@ -1073,36 +1118,19 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1073
1118
|
}
|
|
1074
1119
|
}
|
|
1075
1120
|
catch {
|
|
1076
|
-
// best-effort
|
|
1121
|
+
// best-effort
|
|
1077
1122
|
}
|
|
1078
|
-
// Seed app/generated/register
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
content: `/* AUTO-GENERATED placeholder; will be rewritten by \`smoz register\` */\nexport {};\n`,
|
|
1084
|
-
},
|
|
1085
|
-
{
|
|
1086
|
-
path: path.join(genDir, 'register.openapi.ts'),
|
|
1087
|
-
content: `/* AUTO-GENERATED placeholder; will be rewritten by \`smoz register\` */\nexport {};\n`,
|
|
1088
|
-
},
|
|
1089
|
-
{
|
|
1090
|
-
path: path.join(genDir, 'register.serverless.ts'),
|
|
1091
|
-
content: `/* AUTO-GENERATED placeholder; will be rewritten by \`smoz register\` */\nexport {};\n`,
|
|
1092
|
-
},
|
|
1093
|
-
];
|
|
1094
|
-
for (const s of seeds) {
|
|
1095
|
-
const { created: c } = await writeIfAbsent(s.path, s.content);
|
|
1096
|
-
if (c)
|
|
1097
|
-
created.push(path.posix.normalize(s.path));
|
|
1098
|
-
else
|
|
1099
|
-
skipped.push(path.posix.normalize(s.path));
|
|
1123
|
+
// Seed app/generated/register.* placeholders
|
|
1124
|
+
{
|
|
1125
|
+
const res = await seedRegisterPlaceholders(root);
|
|
1126
|
+
created.push(...res.created);
|
|
1127
|
+
skipped.push(...res.skipped);
|
|
1100
1128
|
}
|
|
1101
1129
|
// 3) package.json presence (create when missing)
|
|
1102
1130
|
const pkgPath = path.join(root, 'package.json');
|
|
1103
1131
|
let pkg = await readJson(pkgPath);
|
|
1104
1132
|
if (!pkg) {
|
|
1105
|
-
const name =
|
|
1133
|
+
const name = toPosixSep(root).split('/').pop() ?? 'smoz-app';
|
|
1106
1134
|
pkg = {
|
|
1107
1135
|
name,
|
|
1108
1136
|
private: true,
|
|
@@ -1110,33 +1138,39 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1110
1138
|
version: '0.0.0',
|
|
1111
1139
|
scripts: {},
|
|
1112
1140
|
};
|
|
1113
|
-
|
|
1114
|
-
if (!dryRunCreate)
|
|
1141
|
+
if (!optAll.dryRun)
|
|
1115
1142
|
await writeJson(pkgPath, pkg);
|
|
1116
1143
|
created.push(path.posix.normalize(pkgPath));
|
|
1117
|
-
}
|
|
1118
|
-
//
|
|
1144
|
+
}
|
|
1145
|
+
// 4) Merge manifest additively (prefer template's embedded package.json)
|
|
1119
1146
|
const templatePkgPath = path.resolve(srcBase, 'package.json');
|
|
1120
1147
|
const manifest = fs.existsSync(templatePkgPath)
|
|
1121
1148
|
? await readJson(templatePkgPath)
|
|
1122
1149
|
: await readJson(path.resolve(templatesBase, '.manifests', `package.${template}.json`));
|
|
1150
|
+
let pkgChanged = false;
|
|
1123
1151
|
if (manifest) {
|
|
1124
|
-
const before = JSON.stringify(pkg);
|
|
1125
1152
|
const added = mergeAdditive(pkg, manifest);
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
await writeJson(pkgPath, pkg);
|
|
1153
|
+
if (added.length > 0) {
|
|
1154
|
+
pkgChanged = true;
|
|
1155
|
+
merged.push(...added);
|
|
1130
1156
|
}
|
|
1131
1157
|
}
|
|
1158
|
+
// 4.5) Ensure runtime dependency on @karmaniverous/smoz is present
|
|
1159
|
+
{
|
|
1160
|
+
const injected = await ensureToolkitDependency(pkg, templatesBase);
|
|
1161
|
+
if (injected) {
|
|
1162
|
+
merged.push(injected);
|
|
1163
|
+
pkgChanged = true;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (!optAll.dryRun && pkgChanged)
|
|
1167
|
+
await writeJson(pkgPath, pkg);
|
|
1132
1168
|
// 5) Optional install
|
|
1133
1169
|
let installed = 'skipped';
|
|
1134
|
-
//
|
|
1170
|
+
// Policy:
|
|
1135
1171
|
// -y implies auto install unless --no-install
|
|
1136
1172
|
const installOpt = optAll.install ?? false;
|
|
1137
1173
|
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
1174
|
const hasInstallString = typeof installOpt === 'string' && installOpt.trim() !== '';
|
|
1141
1175
|
const pm = hasInstallString
|
|
1142
1176
|
? installOpt
|
|
@@ -1144,13 +1178,7 @@ const runInit = async (root, template = 'default', opts) => {
|
|
|
1144
1178
|
? detectPm(root)
|
|
1145
1179
|
: undefined;
|
|
1146
1180
|
installed = pm ? runInstall(root, pm) : installed;
|
|
1147
|
-
return {
|
|
1148
|
-
created,
|
|
1149
|
-
skipped,
|
|
1150
|
-
examples,
|
|
1151
|
-
merged,
|
|
1152
|
-
installed,
|
|
1153
|
-
};
|
|
1181
|
+
return { created, skipped, examples, merged, installed };
|
|
1154
1182
|
};
|
|
1155
1183
|
|
|
1156
1184
|
/**
|
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 .",
|
|
@@ -185,5 +182,5 @@
|
|
|
185
182
|
"templates:lint": "eslint --fix -c templates/default/eslint.config.ts \"templates/default/**/*.{ts,tsx,js,jsx}\" && eslint --fix templates/default/eslint.config.ts"
|
|
186
183
|
},
|
|
187
184
|
"type": "module",
|
|
188
|
-
"version": "0.2.
|
|
185
|
+
"version": "0.2.1"
|
|
189
186
|
}
|