@geekmidas/cli 10.0.0-alpha.5 → 10.0.0-alpha.51
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 +196 -257
- package/bin/adjacent-tsconfig.mjs +97 -0
- package/bin/gkm.mjs +16 -2
- package/bin/owning-tsconfig-jsx-register.mjs +18 -0
- package/bin/owning-tsconfig-jsx.mjs +108 -0
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
- package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-GF6NAZAK.mjs} +20 -21
- package/dist/HostingerProvider-GF6NAZAK.mjs.map +1 -0
- package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
- package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
- package/dist/{openapi-CA0yJUTN.cjs → OpenApiTsGenerator-BN0PIZxd.cjs} +157 -372
- package/dist/OpenApiTsGenerator-BN0PIZxd.cjs.map +1 -0
- package/dist/{openapi-kkQbsRVj.mjs → OpenApiTsGenerator-BzIGy8KE.mjs} +128 -324
- package/dist/OpenApiTsGenerator-BzIGy8KE.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
- package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
- package/dist/aws-DkHdTV-p.cjs +61 -0
- package/dist/aws-DkHdTV-p.cjs.map +1 -0
- package/dist/aws-DojzQnLd.mjs +61 -0
- package/dist/aws-DojzQnLd.mjs.map +1 -0
- package/dist/backends-B9V2XD2M.cjs +82 -0
- package/dist/backends-B9V2XD2M.cjs.map +1 -0
- package/dist/backends-CSuLl0YP.mjs +59 -0
- package/dist/backends-CSuLl0YP.mjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler--fyybf1N.mjs} +19 -30
- package/dist/bundler--fyybf1N.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-By6_sXiL.cjs} +20 -32
- package/dist/bundler-By6_sXiL.cjs.map +1 -0
- package/dist/{config-C21ocYNd.cjs → config-Cvr5fCbn.cjs} +210 -210
- package/dist/config-Cvr5fCbn.cjs.map +1 -0
- package/dist/{config-WRdsQYZK.mjs → config-SOkDH-n1.mjs} +143 -154
- package/dist/config-SOkDH-n1.mjs.map +1 -0
- package/dist/config.cjs +9 -6
- package/dist/config.d.cts +62 -33
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +62 -33
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +9 -2
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +13 -2
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +3 -3
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/{discover-CAlFgYec.mjs → discover-Cf2jAcgF.mjs} +56 -63
- package/dist/discover-Cf2jAcgF.mjs.map +1 -0
- package/dist/{discover-DTmFeloL.cjs → discover-DXNviAh-.cjs} +86 -110
- package/dist/discover-DXNviAh-.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-C1e8qD08.mjs} +13 -26
- package/dist/emit-C1e8qD08.mjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-Dmc1nvdS.cjs} +31 -57
- package/dist/emit-Dmc1nvdS.cjs.map +1 -0
- package/dist/encryption-Bt8SHRI6.cjs +91 -0
- package/dist/encryption-Bt8SHRI6.cjs.map +1 -0
- package/dist/encryption-DRCXp-U4.mjs +90 -0
- package/dist/encryption-DRCXp-U4.mjs.map +1 -0
- package/dist/github-BGx4U61b.cjs +213 -0
- package/dist/github-BGx4U61b.cjs.map +1 -0
- package/dist/github-U_frvZgD.mjs +213 -0
- package/dist/github-U_frvZgD.mjs.map +1 -0
- package/dist/{index-CHB4tSzX.d.cts → index-DYh5TrsZ.d.mts} +4 -23
- package/dist/index-DYh5TrsZ.d.mts.map +1 -0
- package/dist/{index-BThUzEIp.d.mts → index-fU3qHy7c.d.cts} +4 -23
- package/dist/index-fU3qHy7c.d.cts.map +1 -0
- package/dist/index.cjs +9561 -7232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +9479 -7177
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-dx4I0epp.cjs +743 -0
- package/dist/migrate-dx4I0epp.cjs.map +1 -0
- package/dist/migrate-soUQtku3.mjs +736 -0
- package/dist/migrate-soUQtku3.mjs.map +1 -0
- package/dist/openapi.cjs +198 -8
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +53 -15
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +53 -15
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +193 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-WfT-eWlm.mjs +861 -0
- package/dist/ready-WfT-eWlm.mjs.map +1 -0
- package/dist/ready-lr2Fm-ep.cjs +932 -0
- package/dist/ready-lr2Fm-ep.cjs.map +1 -0
- package/dist/reconcile/public.cjs +3 -5
- package/dist/reconcile/public.d.cts +30 -33
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +30 -33
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{reconcile-CFs8C_1a.mjs → reconcile--I7V4Klj.mjs} +4 -7
- package/dist/reconcile--I7V4Klj.mjs.map +1 -0
- package/dist/{reconcile-D0pimNiy.cjs → reconcile-CuCQBa5-.cjs} +5 -8
- package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/storage-BzaUxmqe.mjs +124 -0
- package/dist/storage-BzaUxmqe.mjs.map +1 -0
- package/dist/storage-CSGoC3bK.cjs +152 -0
- package/dist/storage-CSGoC3bK.cjs.map +1 -0
- package/dist/store-DQgxT_WR.cjs +211 -0
- package/dist/store-DQgxT_WR.cjs.map +1 -0
- package/dist/store-DeGATCS-.mjs +182 -0
- package/dist/store-DeGATCS-.mjs.map +1 -0
- package/dist/types-CT0ymlBk.d.mts +6230 -0
- package/dist/types-CT0ymlBk.d.mts.map +1 -0
- package/dist/types-DYDT4FfE.d.cts +6231 -0
- package/dist/types-DYDT4FfE.d.cts.map +1 -0
- package/dist/vitest.cjs +65 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +29 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +29 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +65 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workspace/index.cjs +3 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/{workspace-BcucjwvA.cjs → workspace-BcCiLzTO.cjs} +1040 -1120
- package/dist/workspace-BcCiLzTO.cjs.map +1 -0
- package/dist/{credentials-BBIRJVp4.mjs → workspace-Dp2zK_Nk.mjs} +2212 -1732
- package/dist/workspace-Dp2zK_Nk.mjs.map +1 -0
- package/dist/{credentials-G1JwBoXH.cjs → workspace-GBzR_hID.cjs} +2348 -1849
- package/dist/workspace-GBzR_hID.cjs.map +1 -0
- package/dist/{workspace-BFMrAKqI.mjs → workspace-bOsks1uk.mjs} +871 -974
- package/dist/workspace-bOsks1uk.mjs.map +1 -0
- package/package.json +29 -20
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-C21ocYNd.cjs.map +0 -1
- package/dist/config-WRdsQYZK.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BBIRJVp4.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-G1JwBoXH.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-BThUzEIp.d.mts.map +0 -1
- package/dist/index-CHB4tSzX.d.cts.map +0 -1
- package/dist/openapi-CA0yJUTN.cjs.map +0 -1
- package/dist/openapi-kkQbsRVj.mjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-B5_oHHUj.cjs +0 -11
- package/dist/setup-Cm2UnAdh.cjs +0 -473
- package/dist/setup-Cm2UnAdh.cjs.map +0 -1
- package/dist/setup-DXvRLTZN.mjs +0 -443
- package/dist/setup-DXvRLTZN.mjs.map +0 -1
- package/dist/setup-ZijS4qeC.mjs +0 -11
- package/dist/storage-BJmzYxt8.cjs +0 -363
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs +0 -297
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-C-arnQ1r.d.mts +0 -2184
- package/dist/types-C-arnQ1r.d.mts.map +0 -1
- package/dist/types-abJQuk7m.d.cts +0 -2184
- package/dist/types-abJQuk7m.d.cts.map +0 -1
- package/dist/workspace-BFMrAKqI.mjs.map +0 -1
- package/dist/workspace-BcucjwvA.cjs.map +0 -1
- package/dist/workspace-DdJss8I6.cjs +0 -9
- package/dist/workspace-k4l3rsFh.mjs +0 -8
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a module's path aliases through the tsconfig beside it.
|
|
3
|
+
*
|
|
4
|
+
* tsx applies one tsconfig to the whole process — the one in the directory the
|
|
5
|
+
* command ran from. The workspace's `constructs` glob loads every app's code
|
|
6
|
+
* from wherever a command started, so `~/router.ts` in `apps/api` was being
|
|
7
|
+
* read with `apps/web`'s `~` when the command ran there, and resolved to
|
|
8
|
+
* nothing. TypeScript itself resolves an import through the tsconfig that owns
|
|
9
|
+
* the importing file; this does the same at runtime.
|
|
10
|
+
*
|
|
11
|
+
* Registered after tsx, so it runs first. It only answers a non-relative
|
|
12
|
+
* specifier from TypeScript source outside `node_modules` that the adjacent
|
|
13
|
+
* tsconfig's `paths` actually map — everything else, and anything a mapped
|
|
14
|
+
* candidate fails to resolve to, goes to the next resolver untouched.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
+
import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
|
|
20
|
+
|
|
21
|
+
/** Directory → the paths matcher of the tsconfig that owns it, if any. */
|
|
22
|
+
const matchers = new Map();
|
|
23
|
+
/** Shared with `get-tsconfig`, so a config `extends` is read once. */
|
|
24
|
+
const tsconfigCache = new Map();
|
|
25
|
+
|
|
26
|
+
const TS_SOURCE = /\.(?:[cm]?ts|tsx)$/;
|
|
27
|
+
/** Relative, absolute, or carrying a scheme (`node:`, `file:`, `data:`). */
|
|
28
|
+
const NOT_BARE = /^(?:\.{1,2}(?:\/|$)|\/|[a-zA-Z][a-zA-Z\d+.-]*:)/;
|
|
29
|
+
|
|
30
|
+
function matcherFor(directory) {
|
|
31
|
+
if (!matchers.has(directory)) {
|
|
32
|
+
const tsconfig = getTsconfig(directory, 'tsconfig.json', tsconfigCache);
|
|
33
|
+
matchers.set(directory, tsconfig ? createPathsMatcher(tsconfig) : null);
|
|
34
|
+
}
|
|
35
|
+
return matchers.get(directory);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The files a specifier maps to through the importing module's own tsconfig,
|
|
40
|
+
* or nothing when this hook has no business with it.
|
|
41
|
+
*/
|
|
42
|
+
function candidatesFor(specifier, parent) {
|
|
43
|
+
if (!parent?.startsWith('file:') || NOT_BARE.test(specifier)) return [];
|
|
44
|
+
|
|
45
|
+
const parentPath = fileURLToPath(parent);
|
|
46
|
+
if (!TS_SOURCE.test(parentPath) || parentPath.includes('/node_modules/')) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return matcherFor(dirname(parentPath))?.(specifier) ?? [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** For `module.register()` — the off-thread hooks. */
|
|
54
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
55
|
+
for (const candidate of candidatesFor(specifier, context.parentURL)) {
|
|
56
|
+
try {
|
|
57
|
+
return await nextResolve(pathToFileURL(candidate).href, context);
|
|
58
|
+
} catch {
|
|
59
|
+
// The next mapping, as TypeScript tries them in order.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return nextResolve(specifier, context);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* For `module.registerHooks()` — the in-thread hooks.
|
|
68
|
+
*
|
|
69
|
+
* tsx 4.23 resolves through these, and they are consulted before anything
|
|
70
|
+
* `register()` installed: its resolver threw on a mapped alias and the
|
|
71
|
+
* off-thread hook above was never asked. Registered the same way, after tsx,
|
|
72
|
+
* this one is asked first again.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveSync(specifier, context, nextResolve) {
|
|
75
|
+
for (const candidate of candidatesFor(specifier, context.parentURL)) {
|
|
76
|
+
try {
|
|
77
|
+
return nextResolve(pathToFileURL(candidate).href, context);
|
|
78
|
+
} catch {
|
|
79
|
+
// The next mapping, as TypeScript tries them in order.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return nextResolve(specifier, context);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Install this hook the way the running tsx installed its own, so it is asked
|
|
88
|
+
* before tsx's resolver rather than after it.
|
|
89
|
+
*/
|
|
90
|
+
export async function registerAdjacentTsconfig() {
|
|
91
|
+
const { register, registerHooks } = await import('node:module');
|
|
92
|
+
if (registerHooks) {
|
|
93
|
+
registerHooks({ resolve: resolveSync });
|
|
94
|
+
} else {
|
|
95
|
+
register(import.meta.url);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/bin/gkm.mjs
CHANGED
|
@@ -19,6 +19,10 @@ if (
|
|
|
19
19
|
const { createRequire } = await import('node:module');
|
|
20
20
|
const require = createRequire(import.meta.url);
|
|
21
21
|
const tsxPath = pathToFileURL(require.resolve('tsx')).href;
|
|
22
|
+
// Before tsx, so tsx reads each `.tsx` through it and compiles its JSX the
|
|
23
|
+
// way the file's own tsconfig says. See `owning-tsconfig-jsx.mjs`.
|
|
24
|
+
const jsxPath = new URL('./owning-tsconfig-jsx-register.mjs', import.meta.url)
|
|
25
|
+
.href;
|
|
22
26
|
|
|
23
27
|
const { execFileSync } = await import('node:child_process');
|
|
24
28
|
try {
|
|
@@ -26,7 +30,11 @@ if (
|
|
|
26
30
|
stdio: 'inherit',
|
|
27
31
|
env: {
|
|
28
32
|
...process.env,
|
|
29
|
-
|
|
33
|
+
// `--import=<url>`, never `--import <url>`: Next.js re-parses
|
|
34
|
+
// NODE_OPTIONS for its workers, and with two space-separated imports
|
|
35
|
+
// its parser glued them into one specifier `next build` could not find.
|
|
36
|
+
NODE_OPTIONS:
|
|
37
|
+
`${nodeOptions} --import=${jsxPath} --import=${tsxPath}`.trim(),
|
|
30
38
|
},
|
|
31
39
|
});
|
|
32
40
|
} catch (e) {
|
|
@@ -35,5 +43,11 @@ if (
|
|
|
35
43
|
process.exit(0);
|
|
36
44
|
}
|
|
37
45
|
|
|
38
|
-
// tsx is loaded
|
|
46
|
+
// tsx is loaded. Resolve each module's path aliases through its own tsconfig
|
|
47
|
+
// rather than the one tsx took from the cwd — registered after tsx, so it is
|
|
48
|
+
// asked first. See `adjacent-tsconfig.mjs`.
|
|
49
|
+
const { registerAdjacentTsconfig } = await import('./adjacent-tsconfig.mjs');
|
|
50
|
+
await registerAdjacentTsconfig();
|
|
51
|
+
|
|
52
|
+
// Run the CLI
|
|
39
53
|
await import('../dist/index.mjs');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `--import` that installs `owning-tsconfig-jsx.mjs` — it must come before
|
|
3
|
+
* tsx's own `--import`, so that tsx reads the source through it.
|
|
4
|
+
*
|
|
5
|
+
* Registered with `module.register()` whatever tsx chose: tsx's in-thread hooks
|
|
6
|
+
* (`registerHooks`, Node 22.22.3+ / 24.11.1+) hand off to the off-thread chain
|
|
7
|
+
* when they load a file, and on older Node tsx registers off-thread too, after
|
|
8
|
+
* this — so either way this hook is asked after tsx, for the file's source.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { register } from 'node:module';
|
|
12
|
+
import { isInternalThread } from 'node:worker_threads';
|
|
13
|
+
|
|
14
|
+
// As tsx guards its own: where Node runs preloads in the hooks thread too,
|
|
15
|
+
// registering from there would hook the hooks.
|
|
16
|
+
if (!isInternalThread) {
|
|
17
|
+
register('./owning-tsconfig-jsx.mjs', import.meta.url);
|
|
18
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile a `.tsx` file's JSX the way the tsconfig that owns it says to.
|
|
3
|
+
*
|
|
4
|
+
* tsx applies one tsconfig to the whole process — the one in the directory the
|
|
5
|
+
* command ran from — and only to the files that tsconfig's `include` covers.
|
|
6
|
+
* `gkm dev` runs each app from its own directory, so a construct's template in
|
|
7
|
+
* the workspace (`constructs/templates/MagicLink.tsx`) was outside the app's
|
|
8
|
+
* `include` and compiled with esbuild's defaults: the classic runtime, which
|
|
9
|
+
* calls `React.createElement` in a file that never imported React, because the
|
|
10
|
+
* root tsconfig's `"jsx": "react-jsx"` said it need not. It threw
|
|
11
|
+
* `React is not defined` at render under `gkm dev` and rendered under
|
|
12
|
+
* `gkm test`, where Vitest compiles each file with its nearest tsconfig.
|
|
13
|
+
*
|
|
14
|
+
* Pointing tsx at the root tsconfig instead would only move the problem: an
|
|
15
|
+
* app may set its own `jsx` or `jsxImportSource` (a Hono app's `hono/jsx`), and
|
|
16
|
+
* one tsconfig per process is the same mistake `adjacent-tsconfig.mjs` exists
|
|
17
|
+
* to undo for path aliases. tsx has no per-file tsconfig, but esbuild — which
|
|
18
|
+
* tsx compiles with — honours JSX pragmas in a file over its options. So this
|
|
19
|
+
* hook, registered before tsx so that it sits between tsx and the file on disk,
|
|
20
|
+
* appends the owning tsconfig's JSX settings to the source as pragmas.
|
|
21
|
+
*
|
|
22
|
+
* The owning tsconfig is the nearest one above the file, as Vitest and
|
|
23
|
+
* `adjacent-tsconfig.mjs` choose it. A file that already carries its own JSX
|
|
24
|
+
* pragma, or whose tsconfig asks for nothing a pragma can say (`preserve`, or
|
|
25
|
+
* no `jsx` at all), is left exactly as it is. The pragma goes at the end, so no
|
|
26
|
+
* line of the file moves and its stack traces still point at the right place.
|
|
27
|
+
*
|
|
28
|
+
* Only ESM sources pass through here: for a CommonJS module tsx reads the file
|
|
29
|
+
* itself, and those keep the process tsconfig's behaviour.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { dirname } from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
35
|
+
|
|
36
|
+
const JSX_SOURCE = /\.[jt]sx$/;
|
|
37
|
+
/** A pragma the file chose for itself, which this hook never overrides. */
|
|
38
|
+
const OWN_PRAGMA = /@jsx(?:Runtime|ImportSource|Frag)?\s/;
|
|
39
|
+
|
|
40
|
+
/** Directory → the pragma its owning tsconfig implies, or `null`. */
|
|
41
|
+
const pragmas = new Map();
|
|
42
|
+
/** Shared with `get-tsconfig`, so a config `extends` is read once. */
|
|
43
|
+
const tsconfigCache = new Map();
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The pragma comment that tells esbuild what these compiler options tell
|
|
47
|
+
* TypeScript, or `null` when there is nothing to say.
|
|
48
|
+
*/
|
|
49
|
+
export function jsxPragma(compilerOptions = {}) {
|
|
50
|
+
const jsx = compilerOptions.jsx?.toLowerCase();
|
|
51
|
+
|
|
52
|
+
if (jsx === 'react-jsx' || jsx === 'react-jsxdev') {
|
|
53
|
+
const source = compilerOptions.jsxImportSource ?? 'react';
|
|
54
|
+
return `/* @jsxRuntime automatic @jsxImportSource ${source} */`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (jsx === 'react') {
|
|
58
|
+
const parts = ['@jsxRuntime classic'];
|
|
59
|
+
if (compilerOptions.jsxFactory) {
|
|
60
|
+
parts.push(`@jsx ${compilerOptions.jsxFactory}`);
|
|
61
|
+
}
|
|
62
|
+
if (compilerOptions.jsxFragmentFactory) {
|
|
63
|
+
parts.push(`@jsxFrag ${compilerOptions.jsxFragmentFactory}`);
|
|
64
|
+
}
|
|
65
|
+
return `/* ${parts.join(' ')} */`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// `preserve` and `react-native` leave JSX in the output, which no pragma
|
|
69
|
+
// can ask for; without `jsx` TypeScript compiles no `.tsx` at all.
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function pragmaFor(directory) {
|
|
74
|
+
if (!pragmas.has(directory)) {
|
|
75
|
+
const tsconfig = getTsconfig(directory, 'tsconfig.json', tsconfigCache);
|
|
76
|
+
pragmas.set(directory, jsxPragma(tsconfig?.config.compilerOptions));
|
|
77
|
+
}
|
|
78
|
+
return pragmas.get(directory);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The JSX file at this URL, when it is one of the project's own. */
|
|
82
|
+
function jsxFilePath(url) {
|
|
83
|
+
if (!url.startsWith('file:')) return null;
|
|
84
|
+
|
|
85
|
+
const path = fileURLToPath(url.split(/[?#]/)[0]);
|
|
86
|
+
if (!JSX_SOURCE.test(path) || path.includes('/node_modules/')) return null;
|
|
87
|
+
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** For `module.register()`, so it runs behind tsx however tsx registered. */
|
|
92
|
+
export async function load(url, context, nextLoad) {
|
|
93
|
+
const loaded = await nextLoad(url, context);
|
|
94
|
+
|
|
95
|
+
const path = jsxFilePath(url);
|
|
96
|
+
if (!path || loaded.source == null) return loaded;
|
|
97
|
+
|
|
98
|
+
const pragma = pragmaFor(dirname(path));
|
|
99
|
+
if (!pragma) return loaded;
|
|
100
|
+
|
|
101
|
+
const source =
|
|
102
|
+
typeof loaded.source === 'string'
|
|
103
|
+
? loaded.source
|
|
104
|
+
: new TextDecoder().decode(loaded.source);
|
|
105
|
+
if (OWN_PRAGMA.test(source)) return loaded;
|
|
106
|
+
|
|
107
|
+
return { ...loaded, source: `${source}\n${pragma}\n` };
|
|
108
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const require_index = require("./index.cjs");
|
|
3
2
|
//#region src/deploy/dns/hostinger-api.ts
|
|
4
3
|
/**
|
|
5
4
|
* Hostinger DNS API client
|
|
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
|
12
11
|
* Hostinger API error
|
|
13
12
|
*/
|
|
14
13
|
var HostingerApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
statusText;
|
|
16
|
+
errors;
|
|
15
17
|
constructor(message, status, statusText, errors) {
|
|
16
18
|
super(message);
|
|
17
19
|
this.status = status;
|
|
@@ -65,7 +67,7 @@ var HostingerApi = class {
|
|
|
65
67
|
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
66
68
|
}
|
|
67
69
|
const text = await response.text();
|
|
68
|
-
if (!text || text.trim() === "") return
|
|
70
|
+
if (!text || text.trim() === "") return;
|
|
69
71
|
return JSON.parse(text);
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
@@ -122,8 +124,7 @@ var HostingerApi = class {
|
|
|
122
124
|
* @param type - Record type (e.g., 'A')
|
|
123
125
|
*/
|
|
124
126
|
async recordExists(domain, name, type = "A") {
|
|
125
|
-
|
|
126
|
-
return records.some((r) => r.name === name && r.type === type);
|
|
127
|
+
return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Create a single A record if it doesn't exist
|
|
@@ -135,8 +136,7 @@ var HostingerApi = class {
|
|
|
135
136
|
* @returns true if created, false if already exists
|
|
136
137
|
*/
|
|
137
138
|
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
138
|
-
|
|
139
|
-
if (exists) return false;
|
|
139
|
+
if (await this.recordExists(domain, subdomain, "A")) return false;
|
|
140
140
|
await this.upsertRecords(domain, [{
|
|
141
141
|
name: subdomain,
|
|
142
142
|
type: "A",
|
|
@@ -146,10 +146,14 @@ var HostingerApi = class {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
|
|
150
149
|
//#endregion
|
|
151
150
|
//#region src/deploy/dns/HostingerProvider.ts
|
|
152
151
|
/**
|
|
152
|
+
* Hostinger DNS Provider
|
|
153
|
+
*
|
|
154
|
+
* Implements DnsProvider interface using the Hostinger DNS API.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
153
157
|
* Hostinger DNS provider implementation.
|
|
154
158
|
*/
|
|
155
159
|
var HostingerProvider = class {
|
|
@@ -160,15 +164,13 @@ var HostingerProvider = class {
|
|
|
160
164
|
*/
|
|
161
165
|
async getApi() {
|
|
162
166
|
if (this.api) return this.api;
|
|
163
|
-
const token = await
|
|
167
|
+
const token = await require_index.getHostingerToken();
|
|
164
168
|
if (!token) throw new Error("Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api");
|
|
165
169
|
this.api = new HostingerApi(token);
|
|
166
170
|
return this.api;
|
|
167
171
|
}
|
|
168
172
|
async getRecords(domain) {
|
|
169
|
-
|
|
170
|
-
const records = await api.getRecords(domain);
|
|
171
|
-
return records.map((r) => ({
|
|
173
|
+
return (await (await this.getApi()).getRecords(domain)).map((r) => ({
|
|
172
174
|
name: r.name,
|
|
173
175
|
type: r.type,
|
|
174
176
|
ttl: r.ttl,
|
|
@@ -227,18 +229,15 @@ var HostingerProvider = class {
|
|
|
227
229
|
error: String(error)
|
|
228
230
|
});
|
|
229
231
|
}
|
|
230
|
-
for (const record of records) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
notFound: true
|
|
236
|
-
});
|
|
237
|
-
}
|
|
232
|
+
for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
|
|
233
|
+
record,
|
|
234
|
+
deleted: false,
|
|
235
|
+
notFound: true
|
|
236
|
+
});
|
|
238
237
|
return results;
|
|
239
238
|
}
|
|
240
239
|
};
|
|
241
|
-
|
|
242
240
|
//#endregion
|
|
243
241
|
exports.HostingerProvider = HostingerProvider;
|
|
244
|
-
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=HostingerProvider-BZFGYYVA.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostingerProvider-BZFGYYVA.cjs","names":["getHostingerToken"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { getHostingerToken } from "./
|
|
2
|
-
|
|
1
|
+
import { o as getHostingerToken } from "./index.mjs";
|
|
3
2
|
//#region src/deploy/dns/hostinger-api.ts
|
|
4
3
|
/**
|
|
5
4
|
* Hostinger DNS API client
|
|
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
|
12
11
|
* Hostinger API error
|
|
13
12
|
*/
|
|
14
13
|
var HostingerApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
statusText;
|
|
16
|
+
errors;
|
|
15
17
|
constructor(message, status, statusText, errors) {
|
|
16
18
|
super(message);
|
|
17
19
|
this.status = status;
|
|
@@ -65,7 +67,7 @@ var HostingerApi = class {
|
|
|
65
67
|
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
66
68
|
}
|
|
67
69
|
const text = await response.text();
|
|
68
|
-
if (!text || text.trim() === "") return
|
|
70
|
+
if (!text || text.trim() === "") return;
|
|
69
71
|
return JSON.parse(text);
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
@@ -122,8 +124,7 @@ var HostingerApi = class {
|
|
|
122
124
|
* @param type - Record type (e.g., 'A')
|
|
123
125
|
*/
|
|
124
126
|
async recordExists(domain, name, type = "A") {
|
|
125
|
-
|
|
126
|
-
return records.some((r) => r.name === name && r.type === type);
|
|
127
|
+
return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Create a single A record if it doesn't exist
|
|
@@ -135,8 +136,7 @@ var HostingerApi = class {
|
|
|
135
136
|
* @returns true if created, false if already exists
|
|
136
137
|
*/
|
|
137
138
|
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
138
|
-
|
|
139
|
-
if (exists) return false;
|
|
139
|
+
if (await this.recordExists(domain, subdomain, "A")) return false;
|
|
140
140
|
await this.upsertRecords(domain, [{
|
|
141
141
|
name: subdomain,
|
|
142
142
|
type: "A",
|
|
@@ -146,10 +146,14 @@ var HostingerApi = class {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
|
|
150
149
|
//#endregion
|
|
151
150
|
//#region src/deploy/dns/HostingerProvider.ts
|
|
152
151
|
/**
|
|
152
|
+
* Hostinger DNS Provider
|
|
153
|
+
*
|
|
154
|
+
* Implements DnsProvider interface using the Hostinger DNS API.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
153
157
|
* Hostinger DNS provider implementation.
|
|
154
158
|
*/
|
|
155
159
|
var HostingerProvider = class {
|
|
@@ -166,9 +170,7 @@ var HostingerProvider = class {
|
|
|
166
170
|
return this.api;
|
|
167
171
|
}
|
|
168
172
|
async getRecords(domain) {
|
|
169
|
-
|
|
170
|
-
const records = await api.getRecords(domain);
|
|
171
|
-
return records.map((r) => ({
|
|
173
|
+
return (await (await this.getApi()).getRecords(domain)).map((r) => ({
|
|
172
174
|
name: r.name,
|
|
173
175
|
type: r.type,
|
|
174
176
|
ttl: r.ttl,
|
|
@@ -227,18 +229,15 @@ var HostingerProvider = class {
|
|
|
227
229
|
error: String(error)
|
|
228
230
|
});
|
|
229
231
|
}
|
|
230
|
-
for (const record of records) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
notFound: true
|
|
236
|
-
});
|
|
237
|
-
}
|
|
232
|
+
for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
|
|
233
|
+
record,
|
|
234
|
+
deleted: false,
|
|
235
|
+
notFound: true
|
|
236
|
+
});
|
|
238
237
|
return results;
|
|
239
238
|
}
|
|
240
239
|
};
|
|
241
|
-
|
|
242
240
|
//#endregion
|
|
243
241
|
export { HostingerProvider };
|
|
244
|
-
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=HostingerProvider-GF6NAZAK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostingerProvider-GF6NAZAK.mjs","names":[],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
4
|
-
|
|
1
|
+
let node_fs_promises = require("node:fs/promises");
|
|
2
|
+
let node_path = require("node:path");
|
|
5
3
|
//#region src/deploy/LocalStateProvider.ts
|
|
6
4
|
/**
|
|
5
|
+
* Local Filesystem State Provider
|
|
6
|
+
*
|
|
7
|
+
* Stores deployment state in .gkm/deploy-{stage}.json files.
|
|
8
|
+
* This is the default provider for local development.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
7
11
|
* Get the state file path for a stage.
|
|
8
12
|
*/
|
|
9
13
|
function getStateFilePath(workspaceRoot, stage) {
|
|
@@ -15,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
|
|
|
15
19
|
* Stores state in .gkm/deploy-{stage}.json files in the workspace root.
|
|
16
20
|
*/
|
|
17
21
|
var LocalStateProvider = class {
|
|
22
|
+
workspaceRoot;
|
|
18
23
|
constructor(workspaceRoot) {
|
|
19
24
|
this.workspaceRoot = workspaceRoot;
|
|
20
25
|
}
|
|
@@ -37,7 +42,7 @@ var LocalStateProvider = class {
|
|
|
37
42
|
await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
|
|
38
43
|
}
|
|
39
44
|
};
|
|
40
|
-
|
|
41
45
|
//#endregion
|
|
42
46
|
exports.LocalStateProvider = LocalStateProvider;
|
|
43
|
-
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=LocalStateProvider-7HR6SS2t.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalStateProvider-7HR6SS2t.cjs","names":["join","readFile","mkdir","writeFile"],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,QAAA,GAAOA,UAAAA,KAAAA,CAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,OAAA,GAAMD,UAAAA,KAAAA,CAAK,KAAK,eAAe,MAAM;EAG3C,OAAA,GAAME,iBAAAA,MAAAA,CAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,OAAA,GAAMC,iBAAAA,UAAAA,CAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
//#region src/deploy/LocalStateProvider.ts
|
|
5
4
|
/**
|
|
5
|
+
* Local Filesystem State Provider
|
|
6
|
+
*
|
|
7
|
+
* Stores deployment state in .gkm/deploy-{stage}.json files.
|
|
8
|
+
* This is the default provider for local development.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
6
11
|
* Get the state file path for a stage.
|
|
7
12
|
*/
|
|
8
13
|
function getStateFilePath(workspaceRoot, stage) {
|
|
@@ -14,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
|
|
|
14
19
|
* Stores state in .gkm/deploy-{stage}.json files in the workspace root.
|
|
15
20
|
*/
|
|
16
21
|
var LocalStateProvider = class {
|
|
22
|
+
workspaceRoot;
|
|
17
23
|
constructor(workspaceRoot) {
|
|
18
24
|
this.workspaceRoot = workspaceRoot;
|
|
19
25
|
}
|
|
@@ -36,7 +42,7 @@ var LocalStateProvider = class {
|
|
|
36
42
|
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
37
43
|
}
|
|
38
44
|
};
|
|
39
|
-
|
|
40
45
|
//#endregion
|
|
41
46
|
export { LocalStateProvider };
|
|
42
|
-
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=LocalStateProvider-D7QyJoLM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalStateProvider-D7QyJoLM.mjs","names":[],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,OAAO,KAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,MAAM,SAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,MAAM,KAAK,KAAK,eAAe,MAAM;EAG3C,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,MAAM,UAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}
|