@vendure-platform/console-auth 1.0.0-major.202609091053.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +335 -0
- package/dist/browser-auth.d.ts +48 -0
- package/dist/browser-auth.js +1 -0
- package/dist/console-api.d.ts +66 -0
- package/dist/console-api.js +1 -0
- package/dist/console-projects.d.ts +101 -0
- package/dist/console-projects.js +1 -0
- package/dist/credential-store.d.ts +99 -0
- package/dist/credential-store.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/onboarding.d.ts +158 -0
- package/dist/onboarding.js +1 -0
- package/dist/origins.d.ts +23 -0
- package/dist/origins.js +1 -0
- package/dist/product-access.d.ts +48 -0
- package/dist/product-access.js +1 -0
- package/dist/project-link-manifest.d.ts +93 -0
- package/dist/project-link-manifest.js +1 -0
- package/dist/registry.d.ts +30 -0
- package/dist/registry.js +1 -0
- package/dist/session-lock.d.ts +49 -0
- package/dist/session-lock.js +1 -0
- package/dist/session.d.ts +38 -0
- package/dist/session.js +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An account-level Console session.
|
|
3
|
+
*
|
|
4
|
+
* This is deliberately not the same thing as the per-project development
|
|
5
|
+
* credential `@vendure-platform/cli` keeps in `<projectRoot>/.vendure`. That one
|
|
6
|
+
* authenticates one environment of one project and belongs to the project. This
|
|
7
|
+
* one authenticates the person, is worth the same as their Console password
|
|
8
|
+
* across every project they own, and therefore never goes near a project
|
|
9
|
+
* directory that someone is about to `git init`.
|
|
10
|
+
*/
|
|
11
|
+
export interface StoredConsoleCredential {
|
|
12
|
+
schemaVersion: 2;
|
|
13
|
+
/**
|
|
14
|
+
* The Console the session was issued by. A credential minted against a local
|
|
15
|
+
* dev Console must not be replayed against production, so a mismatch reads
|
|
16
|
+
* as "no credential" rather than being offered to the wrong host.
|
|
17
|
+
*/
|
|
18
|
+
origin: string;
|
|
19
|
+
/** The API origin that issued both tokens. Legacy caches have no known issuer. */
|
|
20
|
+
apiOrigin: string;
|
|
21
|
+
accessToken: string;
|
|
22
|
+
/** Absolute expiry, ISO-8601. */
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
refreshToken?: string;
|
|
25
|
+
}
|
|
26
|
+
/** `~/.vendure`. No `XDG_CONFIG_HOME` handling: the repo has no precedent for it. */
|
|
27
|
+
export declare function consoleConfigDir(home?: string): string;
|
|
28
|
+
export declare function consoleCredentialPath(home?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The stored credential, or `undefined`.
|
|
31
|
+
*
|
|
32
|
+
* Every failure reads as "no credential": a missing file, an unreadable one,
|
|
33
|
+
* malformed JSON, and a credential for another Console. The caller's answer to
|
|
34
|
+
* all of those is the same, which is to log in again, and a corrupt cache file
|
|
35
|
+
* is not worth stopping a scaffold over.
|
|
36
|
+
*
|
|
37
|
+
* Expiry is deliberately not checked here. An expired credential may still
|
|
38
|
+
* carry a usable refresh token, so the decision belongs to the resolver.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readConsoleCredential(options?: {
|
|
41
|
+
home?: string;
|
|
42
|
+
origin?: string;
|
|
43
|
+
apiOrigin?: string;
|
|
44
|
+
}): Promise<StoredConsoleCredential | undefined>;
|
|
45
|
+
/** What is on this machine, with no part of either token in it. */
|
|
46
|
+
export interface ConsoleSessionStatus {
|
|
47
|
+
path: string;
|
|
48
|
+
stored: boolean;
|
|
49
|
+
/** Absolute expiry of the access token, ISO-8601. */
|
|
50
|
+
expiresAt?: string;
|
|
51
|
+
/** Whether an expired session renews without a browser. */
|
|
52
|
+
renewable: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Report the stored session without handing the caller either token.
|
|
56
|
+
*
|
|
57
|
+
* `vendure console status` needs to say whether a session is held, and a status
|
|
58
|
+
* line is the last place a token should be able to reach. Returning the
|
|
59
|
+
* credential and trusting every caller to print around it is one slip away from
|
|
60
|
+
* a token in a terminal scrollback or a CI log.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readConsoleSessionStatus(options?: {
|
|
63
|
+
home?: string;
|
|
64
|
+
origin?: string;
|
|
65
|
+
apiOrigin?: string;
|
|
66
|
+
}): Promise<ConsoleSessionStatus>;
|
|
67
|
+
/** Whether the credential is past its expiry, counting the skew. */
|
|
68
|
+
export declare function isExpired(credential: StoredConsoleCredential, now: Date): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Write the credential at mode 0600 through a temporary file and a rename, so a
|
|
71
|
+
* reader never observes a half-written token and a crash cannot leave one
|
|
72
|
+
* behind. `wx` on the temporary file means two concurrent writers cannot land
|
|
73
|
+
* on the same path.
|
|
74
|
+
*
|
|
75
|
+
* Precondition: hold the Console session lock. The rename makes each write
|
|
76
|
+
* whole, and that is all it makes it. It does not stop this write landing on
|
|
77
|
+
* top of a pair another process obtained from the refresh token this one is
|
|
78
|
+
* about to present, which turns a live session into a dead one. Callers inside
|
|
79
|
+
* this package go through `loginAndStoreConsoleToken`, `refreshStoredConsoleToken`
|
|
80
|
+
* or `resolveStoredConsoleToken`, all of which take the lock. A caller outside
|
|
81
|
+
* it has to wrap this in `withConsoleSessionLock` itself. The README section
|
|
82
|
+
* "Sharing the credential across processes" says why.
|
|
83
|
+
*/
|
|
84
|
+
export declare function writeConsoleCredential(credential: StoredConsoleCredential, options?: {
|
|
85
|
+
home?: string;
|
|
86
|
+
}): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Remove the stored credential. Not re-exported from `index.ts`: the one caller
|
|
89
|
+
* is the refusal path in `session.ts`.
|
|
90
|
+
*
|
|
91
|
+
* Precondition: hold the Console session lock, and re-read the credential under
|
|
92
|
+
* it first. A refusal observed without the lock proves nothing about the file.
|
|
93
|
+
* The token it was observed against may already have been replaced by a live
|
|
94
|
+
* pair another process wrote, and removing the file on that evidence throws
|
|
95
|
+
* away a working session.
|
|
96
|
+
*/
|
|
97
|
+
export declare function discardConsoleCredential(options?: {
|
|
98
|
+
home?: string;
|
|
99
|
+
}): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.consoleConfigDir=s,exports.consoleCredentialPath=a,exports.readConsoleCredential=c,exports.readConsoleSessionStatus=p,exports.isExpired=l,exports.writeConsoleCredential=u,exports.discardConsoleCredential=d;const e=require("node:crypto"),r=require("node:fs/promises"),i=require("node:os"),n=require("node:path"),o=require("./origins"),t=6e4;function s(e=(0,i.homedir)()){return(0,n.join)(e,".vendure")}function a(e=(0,i.homedir)()){return(0,n.join)(s(e),"console-credentials.json")}async function c(e={}){const{appOrigin:i,apiOrigin:n}=(0,o.consoleOrigins)({appOrigin:e.origin,apiOrigin:e.apiOrigin});let t;try{t=JSON.parse(await(0,r.readFile)(a(e.home),"utf8"))}catch{return}if(g(t)){try{if((0,o.consoleAppOrigin)(t.origin)!==i||(0,o.consoleApiOrigin)(t.apiOrigin)!==n)return}catch{return}return{...t,origin:i,apiOrigin:n}}}async function p(e={}){const r=a(e.home),i=await c(e);return i?{path:r,stored:!0,expiresAt:i.expiresAt,renewable:Boolean(i.refreshToken)}:{path:r,stored:!1,renewable:!1}}function l(e,r){const i=Date.parse(e.expiresAt);return!!Number.isNaN(i)||i-t<=r.getTime()}async function u(i,t={}){if(!g(i))throw new Error("Refusing to store a malformed Vendure Console credential.");i={...i,origin:(0,o.consoleAppOrigin)(i.origin),apiOrigin:(0,o.consoleApiOrigin)(i.apiOrigin)};const c=s(t.home),p=a(t.home),l=(0,n.join)(c,`.console-credentials.${(0,e.randomUUID)()}.tmp`);let u;try{await(0,r.mkdir)(c,{recursive:!0,mode:448}),await(0,r.chmod)(c,448),u=await(0,r.open)(l,"wx",384),await u.writeFile(`${JSON.stringify(i,null,2)}\n`,"utf8"),await u.sync(),await u.close(),u=void 0,await(0,r.rename)(l,p)}catch{throw await(null==u?void 0:u.close().catch(()=>{})),await(0,r.unlink)(l).catch(()=>{}),new Error("Could not store the Vendure Console credential securely.")}}async function d(e={}){await(0,r.unlink)(a(e.home)).catch(()=>{})}function g(e){if(!e||"object"!=typeof e||Array.isArray(e))return!1;const r=e;return!(2!==r.schemaVersion||"string"!=typeof r.origin||!r.origin||"string"!=typeof r.apiOrigin||!r.apiOrigin||"string"!=typeof r.accessToken||!r.accessToken||"string"!=typeof r.expiresAt||Number.isNaN(Date.parse(r.expiresAt))||void 0!==r.refreshToken&&("string"!=typeof r.refreshToken||!r.refreshToken))}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ConsoleTokenGrantError, loginWithBrowser, refreshSession, type BrowserLoginOptions, type ConsoleAuthClient, type ConsoleSession, } from './browser-auth';
|
|
2
|
+
export { ConsoleApi, ConsoleApiError, ConsoleApiTransportError, isConsoleConflict, isPermissionDenied, type ConsoleApiOptions, type ConsoleApiRequestOptions, } from './console-api';
|
|
3
|
+
export { EVALUATION_OFFER_CODES, PROJECT_EDITIONS, createProject, createRegistryAccessToken, evaluationDenial, getProjectEvaluation, getRegistryAccess, linkProject, listProjectEditions, listRegistryAccessTokens, readViewerAccount, rotateRegistryAccessToken, startProjectEvaluation, type ConsoleProject, type ConsoleProjectEdition, type EvaluationOfferCode, type IssuedRegistryAccessToken, type ProjectEdition, type ProjectEditionSource, type ProjectEvaluationState, type RegistryAccessConfiguration, type RegistryAccessToken, type StartedEvaluation, } from './console-projects';
|
|
4
|
+
export { consoleConfigDir, consoleCredentialPath, isExpired, readConsoleCredential, readConsoleSessionStatus, writeConsoleCredential, type ConsoleSessionStatus, type StoredConsoleCredential, } from './credential-store';
|
|
5
|
+
export { ConsoleOnboardingCancelled, acquireRegistryAccess, establishProjectLink, projectCreateIdempotencyKey, registryAccessTokenName, selectConsoleProject, writeProjectLink, type ConsoleOnboardingInteraction, type EstablishProjectLinkOptions, type EstablishedProjectLink, type RegistryAccessInteraction, type RegistryAccessResult, type SelectedConsoleProject, } from './onboarding';
|
|
6
|
+
export { DEFAULT_CONSOLE_API_ORIGIN, DEFAULT_CONSOLE_APP_ORIGIN, consoleApiOrigin, consoleAppOrigin, consoleOrigins, } from './origins';
|
|
7
|
+
export { getProductAccessSummary, type ProductAccessEvaluation, type ProductAccessSubscription, type ProductAccessSummary, } from './product-access';
|
|
8
|
+
export { PROJECT_LINK_IGNORE_RULE, PROJECT_LINK_KEEP_RULE, PROJECT_LINK_MANIFEST_RELATIVE_PATH, applyProjectLinkGitignoreRules, ensureProjectLinkGitignore, parseProjectLinkManifest, projectLinkManifestPath, readProjectLinkManifest, writeProjectLinkManifest, type ProjectLinkGitignoreResult, type ProjectLinkManifest, type ProjectLinkManifestRead, } from './project-link-manifest';
|
|
9
|
+
export { DEFAULT_REGISTRY_URL, REGISTRY_SCOPE, registryAuthTokenKey, registryLoginUrl, registryUrl, } from './registry';
|
|
10
|
+
export { loginAndStoreConsoleToken, refreshStoredConsoleToken, resolveStoredConsoleToken, type ConsoleSessionOptions, } from './session';
|
|
11
|
+
export { withConsoleSessionLock, type ConsoleSessionLockOptions } from './session-lock';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.DEFAULT_REGISTRY_URL=exports.writeProjectLinkManifest=exports.readProjectLinkManifest=exports.projectLinkManifestPath=exports.parseProjectLinkManifest=exports.ensureProjectLinkGitignore=exports.applyProjectLinkGitignoreRules=exports.PROJECT_LINK_MANIFEST_RELATIVE_PATH=exports.PROJECT_LINK_KEEP_RULE=exports.PROJECT_LINK_IGNORE_RULE=exports.getProductAccessSummary=exports.consoleOrigins=exports.consoleAppOrigin=exports.consoleApiOrigin=exports.DEFAULT_CONSOLE_APP_ORIGIN=exports.DEFAULT_CONSOLE_API_ORIGIN=exports.writeProjectLink=exports.selectConsoleProject=exports.registryAccessTokenName=exports.projectCreateIdempotencyKey=exports.establishProjectLink=exports.acquireRegistryAccess=exports.ConsoleOnboardingCancelled=exports.writeConsoleCredential=exports.readConsoleSessionStatus=exports.readConsoleCredential=exports.isExpired=exports.consoleCredentialPath=exports.consoleConfigDir=exports.startProjectEvaluation=exports.rotateRegistryAccessToken=exports.readViewerAccount=exports.listRegistryAccessTokens=exports.listProjectEditions=exports.linkProject=exports.getRegistryAccess=exports.getProjectEvaluation=exports.evaluationDenial=exports.createRegistryAccessToken=exports.createProject=exports.PROJECT_EDITIONS=exports.EVALUATION_OFFER_CODES=exports.isPermissionDenied=exports.isConsoleConflict=exports.ConsoleApiTransportError=exports.ConsoleApiError=exports.ConsoleApi=exports.refreshSession=exports.loginWithBrowser=exports.ConsoleTokenGrantError=void 0,exports.withConsoleSessionLock=exports.resolveStoredConsoleToken=exports.refreshStoredConsoleToken=exports.loginAndStoreConsoleToken=exports.registryUrl=exports.registryLoginUrl=exports.registryAuthTokenKey=exports.REGISTRY_SCOPE=void 0;var e=require("./browser-auth");Object.defineProperty(exports,"ConsoleTokenGrantError",{enumerable:!0,get:function(){return e.ConsoleTokenGrantError}}),Object.defineProperty(exports,"loginWithBrowser",{enumerable:!0,get:function(){return e.loginWithBrowser}}),Object.defineProperty(exports,"refreshSession",{enumerable:!0,get:function(){return e.refreshSession}});var r=require("./console-api");Object.defineProperty(exports,"ConsoleApi",{enumerable:!0,get:function(){return r.ConsoleApi}}),Object.defineProperty(exports,"ConsoleApiError",{enumerable:!0,get:function(){return r.ConsoleApiError}}),Object.defineProperty(exports,"ConsoleApiTransportError",{enumerable:!0,get:function(){return r.ConsoleApiTransportError}}),Object.defineProperty(exports,"isConsoleConflict",{enumerable:!0,get:function(){return r.isConsoleConflict}}),Object.defineProperty(exports,"isPermissionDenied",{enumerable:!0,get:function(){return r.isPermissionDenied}});var t=require("./console-projects");Object.defineProperty(exports,"EVALUATION_OFFER_CODES",{enumerable:!0,get:function(){return t.EVALUATION_OFFER_CODES}}),Object.defineProperty(exports,"PROJECT_EDITIONS",{enumerable:!0,get:function(){return t.PROJECT_EDITIONS}}),Object.defineProperty(exports,"createProject",{enumerable:!0,get:function(){return t.createProject}}),Object.defineProperty(exports,"createRegistryAccessToken",{enumerable:!0,get:function(){return t.createRegistryAccessToken}}),Object.defineProperty(exports,"evaluationDenial",{enumerable:!0,get:function(){return t.evaluationDenial}}),Object.defineProperty(exports,"getProjectEvaluation",{enumerable:!0,get:function(){return t.getProjectEvaluation}}),Object.defineProperty(exports,"getRegistryAccess",{enumerable:!0,get:function(){return t.getRegistryAccess}}),Object.defineProperty(exports,"linkProject",{enumerable:!0,get:function(){return t.linkProject}}),Object.defineProperty(exports,"listProjectEditions",{enumerable:!0,get:function(){return t.listProjectEditions}}),Object.defineProperty(exports,"listRegistryAccessTokens",{enumerable:!0,get:function(){return t.listRegistryAccessTokens}}),Object.defineProperty(exports,"readViewerAccount",{enumerable:!0,get:function(){return t.readViewerAccount}}),Object.defineProperty(exports,"rotateRegistryAccessToken",{enumerable:!0,get:function(){return t.rotateRegistryAccessToken}}),Object.defineProperty(exports,"startProjectEvaluation",{enumerable:!0,get:function(){return t.startProjectEvaluation}});var o=require("./credential-store");Object.defineProperty(exports,"consoleConfigDir",{enumerable:!0,get:function(){return o.consoleConfigDir}}),Object.defineProperty(exports,"consoleCredentialPath",{enumerable:!0,get:function(){return o.consoleCredentialPath}}),Object.defineProperty(exports,"isExpired",{enumerable:!0,get:function(){return o.isExpired}}),Object.defineProperty(exports,"readConsoleCredential",{enumerable:!0,get:function(){return o.readConsoleCredential}}),Object.defineProperty(exports,"readConsoleSessionStatus",{enumerable:!0,get:function(){return o.readConsoleSessionStatus}}),Object.defineProperty(exports,"writeConsoleCredential",{enumerable:!0,get:function(){return o.writeConsoleCredential}});var n=require("./onboarding");Object.defineProperty(exports,"ConsoleOnboardingCancelled",{enumerable:!0,get:function(){return n.ConsoleOnboardingCancelled}}),Object.defineProperty(exports,"acquireRegistryAccess",{enumerable:!0,get:function(){return n.acquireRegistryAccess}}),Object.defineProperty(exports,"establishProjectLink",{enumerable:!0,get:function(){return n.establishProjectLink}}),Object.defineProperty(exports,"projectCreateIdempotencyKey",{enumerable:!0,get:function(){return n.projectCreateIdempotencyKey}}),Object.defineProperty(exports,"registryAccessTokenName",{enumerable:!0,get:function(){return n.registryAccessTokenName}}),Object.defineProperty(exports,"selectConsoleProject",{enumerable:!0,get:function(){return n.selectConsoleProject}}),Object.defineProperty(exports,"writeProjectLink",{enumerable:!0,get:function(){return n.writeProjectLink}});var s=require("./origins");Object.defineProperty(exports,"DEFAULT_CONSOLE_API_ORIGIN",{enumerable:!0,get:function(){return s.DEFAULT_CONSOLE_API_ORIGIN}}),Object.defineProperty(exports,"DEFAULT_CONSOLE_APP_ORIGIN",{enumerable:!0,get:function(){return s.DEFAULT_CONSOLE_APP_ORIGIN}}),Object.defineProperty(exports,"consoleApiOrigin",{enumerable:!0,get:function(){return s.consoleApiOrigin}}),Object.defineProperty(exports,"consoleAppOrigin",{enumerable:!0,get:function(){return s.consoleAppOrigin}}),Object.defineProperty(exports,"consoleOrigins",{enumerable:!0,get:function(){return s.consoleOrigins}});var i=require("./product-access");Object.defineProperty(exports,"getProductAccessSummary",{enumerable:!0,get:function(){return i.getProductAccessSummary}});var c=require("./project-link-manifest");Object.defineProperty(exports,"PROJECT_LINK_IGNORE_RULE",{enumerable:!0,get:function(){return c.PROJECT_LINK_IGNORE_RULE}}),Object.defineProperty(exports,"PROJECT_LINK_KEEP_RULE",{enumerable:!0,get:function(){return c.PROJECT_LINK_KEEP_RULE}}),Object.defineProperty(exports,"PROJECT_LINK_MANIFEST_RELATIVE_PATH",{enumerable:!0,get:function(){return c.PROJECT_LINK_MANIFEST_RELATIVE_PATH}}),Object.defineProperty(exports,"applyProjectLinkGitignoreRules",{enumerable:!0,get:function(){return c.applyProjectLinkGitignoreRules}}),Object.defineProperty(exports,"ensureProjectLinkGitignore",{enumerable:!0,get:function(){return c.ensureProjectLinkGitignore}}),Object.defineProperty(exports,"parseProjectLinkManifest",{enumerable:!0,get:function(){return c.parseProjectLinkManifest}}),Object.defineProperty(exports,"projectLinkManifestPath",{enumerable:!0,get:function(){return c.projectLinkManifestPath}}),Object.defineProperty(exports,"readProjectLinkManifest",{enumerable:!0,get:function(){return c.readProjectLinkManifest}}),Object.defineProperty(exports,"writeProjectLinkManifest",{enumerable:!0,get:function(){return c.writeProjectLinkManifest}});var u=require("./registry");Object.defineProperty(exports,"DEFAULT_REGISTRY_URL",{enumerable:!0,get:function(){return u.DEFAULT_REGISTRY_URL}}),Object.defineProperty(exports,"REGISTRY_SCOPE",{enumerable:!0,get:function(){return u.REGISTRY_SCOPE}}),Object.defineProperty(exports,"registryAuthTokenKey",{enumerable:!0,get:function(){return u.registryAuthTokenKey}}),Object.defineProperty(exports,"registryLoginUrl",{enumerable:!0,get:function(){return u.registryLoginUrl}}),Object.defineProperty(exports,"registryUrl",{enumerable:!0,get:function(){return u.registryUrl}});var p=require("./session");Object.defineProperty(exports,"loginAndStoreConsoleToken",{enumerable:!0,get:function(){return p.loginAndStoreConsoleToken}}),Object.defineProperty(exports,"refreshStoredConsoleToken",{enumerable:!0,get:function(){return p.refreshStoredConsoleToken}}),Object.defineProperty(exports,"resolveStoredConsoleToken",{enumerable:!0,get:function(){return p.resolveStoredConsoleToken}});var l=require("./session-lock");Object.defineProperty(exports,"withConsoleSessionLock",{enumerable:!0,get:function(){return l.withConsoleSessionLock}});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { ConsoleApi } from './console-api';
|
|
2
|
+
import { type ConsoleProjectEdition, type EvaluationOfferCode, type ProjectEdition, type ProjectEditionSource } from './console-projects';
|
|
3
|
+
import { type ProjectLinkManifest } from './project-link-manifest';
|
|
4
|
+
/**
|
|
5
|
+
* The person stopped the flow. Distinct from a failure: nothing is wrong, and
|
|
6
|
+
* neither `create` nor `vendure console link` should report an error or leave a
|
|
7
|
+
* half-written manifest behind.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ConsoleOnboardingCancelled extends Error {
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Everything the flow has to ask a person, as calls rather than prompts.
|
|
14
|
+
*
|
|
15
|
+
* The flow lives here so `create` and `vendure console link` establish the same
|
|
16
|
+
* link the same way. The prompting does not: `create` draws with
|
|
17
|
+
* `@clack/prompts` and the CLI plugin draws with the Vendure CLI reporter, and
|
|
18
|
+
* pulling either one in here would put a prompt library into both.
|
|
19
|
+
*
|
|
20
|
+
* Any method may throw `ConsoleOnboardingCancelled`.
|
|
21
|
+
*/
|
|
22
|
+
export interface ConsoleOnboardingInteraction {
|
|
23
|
+
/**
|
|
24
|
+
* Which Project this checkout is. Returning `undefined` means "none of
|
|
25
|
+
* these", which creates one.
|
|
26
|
+
*/
|
|
27
|
+
chooseProject(projects: ConsoleProjectEdition[]): Promise<ConsoleProjectEdition | undefined>;
|
|
28
|
+
/** The name for a new Project. */
|
|
29
|
+
newProjectName(): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Which edition to evaluate, from the Offers Console says this Project is
|
|
32
|
+
* eligible for. Returning `undefined` declines, which is an ordinary answer.
|
|
33
|
+
*/
|
|
34
|
+
chooseEvaluation(offers: EvaluationOfferCode[]): Promise<EvaluationOfferCode | undefined>;
|
|
35
|
+
report(message: string): void;
|
|
36
|
+
warn(message: string): void;
|
|
37
|
+
}
|
|
38
|
+
export interface EstablishProjectLinkOptions {
|
|
39
|
+
projectRoot: string;
|
|
40
|
+
api: ConsoleApi;
|
|
41
|
+
interaction: ConsoleOnboardingInteraction;
|
|
42
|
+
/**
|
|
43
|
+
* Keep a Project Link Manifest that is already there and still names a
|
|
44
|
+
* Project the account owns, instead of creating a second link for the same
|
|
45
|
+
* checkout. This is what makes a repeated `vendure console link` a repair:
|
|
46
|
+
* the link identity survives and only the missing credential is replaced.
|
|
47
|
+
*/
|
|
48
|
+
reuseExistingLink?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Sent as `Idempotency-Key` when a Project is created. Defaults to a value
|
|
51
|
+
* derived from the checkout and the chosen name, which is what makes a
|
|
52
|
+
* repeat after a lost response return the first Project instead of making a
|
|
53
|
+
* second one. A random key per run would not: the run that lost the
|
|
54
|
+
* response is the run that has to be repeated.
|
|
55
|
+
*/
|
|
56
|
+
idempotencyKey?: string;
|
|
57
|
+
}
|
|
58
|
+
export interface EstablishedProjectLink {
|
|
59
|
+
manifest: ProjectLinkManifest;
|
|
60
|
+
edition: ProjectEdition | null;
|
|
61
|
+
source: ProjectEditionSource;
|
|
62
|
+
/** The Project was created by this run. */
|
|
63
|
+
createdProject: boolean;
|
|
64
|
+
/** An existing Manifest was kept rather than a new link created. */
|
|
65
|
+
reusedLink: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface SelectedConsoleProject {
|
|
68
|
+
project: ConsoleProjectEdition;
|
|
69
|
+
edition: ProjectEdition | null;
|
|
70
|
+
source: ProjectEditionSource;
|
|
71
|
+
/** The Project was created by this run rather than chosen from the account. */
|
|
72
|
+
createdProject: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Decide which Console Project this is, creating one and giving it an edition
|
|
76
|
+
* when it needs them.
|
|
77
|
+
*
|
|
78
|
+
* Writes no local file, and is not a preflight: answering "create a new one"
|
|
79
|
+
* creates a Project in Console, and accepting an Offer starts an Evaluation.
|
|
80
|
+
* Both outlive the run that made them, so a caller that may not commit to a
|
|
81
|
+
* Project must not call this.
|
|
82
|
+
*
|
|
83
|
+
* Split from writing the link because `@vendure-platform/create` needs the
|
|
84
|
+
* answer before the directory it would write into exists: the edition decides
|
|
85
|
+
* which facade, options and services get scaffolded, and the manifest can only
|
|
86
|
+
* be written once there is somewhere to put it. `vendure console link` runs
|
|
87
|
+
* both halves back to back through `establishProjectLink`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function selectConsoleProject(options: Pick<EstablishProjectLinkOptions, 'api' | 'interaction' | 'projectRoot' | 'idempotencyKey'>): Promise<SelectedConsoleProject>;
|
|
90
|
+
/**
|
|
91
|
+
* Create an approved link for `projectId` and write its Manifest into
|
|
92
|
+
* `projectRoot`, with the `.gitignore` rules that keep the manifest committed
|
|
93
|
+
* and the credential files out.
|
|
94
|
+
*/
|
|
95
|
+
export declare function writeProjectLink(options: Pick<EstablishProjectLinkOptions, 'api' | 'interaction' | 'projectRoot'> & {
|
|
96
|
+
projectId: string;
|
|
97
|
+
}): Promise<ProjectLinkManifest>;
|
|
98
|
+
/**
|
|
99
|
+
* Establish the link between this checkout and a Console Project, and write the
|
|
100
|
+
* Project Link Manifest.
|
|
101
|
+
*
|
|
102
|
+
* Creates a Project and starts an Evaluation when the answers call for it, the
|
|
103
|
+
* same way `selectConsoleProject` does.
|
|
104
|
+
*
|
|
105
|
+
* Writes no credential of either kind. The caller decides which stores it owns:
|
|
106
|
+
* the account-level Console session is already written by the login that got
|
|
107
|
+
* the token this runs on, and the project-scoped Environment Credential belongs
|
|
108
|
+
* to `@vendure-platform/cli`, which is the only package that may write it.
|
|
109
|
+
*/
|
|
110
|
+
export declare function establishProjectLink(options: EstablishProjectLinkOptions): Promise<EstablishedProjectLink>;
|
|
111
|
+
export interface RegistryAccessInteraction {
|
|
112
|
+
/**
|
|
113
|
+
* Whether to rotate the named Access Token. Asked only when a token of the
|
|
114
|
+
* expected name already exists and its secret is not held locally, because
|
|
115
|
+
* rotating invalidates whatever is installing with it today.
|
|
116
|
+
*/
|
|
117
|
+
confirmRotate(tokenName: string): Promise<boolean>;
|
|
118
|
+
report(message: string): void;
|
|
119
|
+
warn(message: string): void;
|
|
120
|
+
}
|
|
121
|
+
export interface RegistryAccessResult {
|
|
122
|
+
registryUrl: string;
|
|
123
|
+
scope: string;
|
|
124
|
+
/** The plaintext token, when one was issued. Console returns it once. */
|
|
125
|
+
token?: string;
|
|
126
|
+
/** Set when no token could be issued and the person has to log in to the Registry. */
|
|
127
|
+
fallback?: 'npm-login';
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The key a repeated create sends, derived from the checkout and the name.
|
|
131
|
+
*
|
|
132
|
+
* Console scopes the key by account, user and name, and answers a repeat that
|
|
133
|
+
* carries a different name with `project.idempotency_conflict`. Deriving the key
|
|
134
|
+
* from both means the same answer to the same question is safe to repeat, and a
|
|
135
|
+
* different answer is a different question.
|
|
136
|
+
*/
|
|
137
|
+
export declare function projectCreateIdempotencyKey(projectRoot: string, name: string): string;
|
|
138
|
+
/** The Access Token name a machine claims, so a repeat finds its own token rather than making a second one. */
|
|
139
|
+
export declare function registryAccessTokenName(host?: string): string;
|
|
140
|
+
/**
|
|
141
|
+
* Get the Registry configuration and, when the session is allowed to, a scoped
|
|
142
|
+
* Access Token for this machine.
|
|
143
|
+
*
|
|
144
|
+
* The create route returns the plaintext once and never again, so a lost
|
|
145
|
+
* response leaves a token that exists in Console and nowhere else. Creating a
|
|
146
|
+
* second one under the same name is refused with
|
|
147
|
+
* `registry_access_token.name_taken`. The recovery is to find the token of that
|
|
148
|
+
* name in the list and rotate it, which is the only way back to a usable
|
|
149
|
+
* secret. Rotating invalidates the old one, so it is never done without asking.
|
|
150
|
+
*/
|
|
151
|
+
export declare function acquireRegistryAccess(options: {
|
|
152
|
+
api: ConsoleApi;
|
|
153
|
+
projectId: string;
|
|
154
|
+
tokenName?: string;
|
|
155
|
+
interaction: RegistryAccessInteraction;
|
|
156
|
+
defaultRegistryUrl: string;
|
|
157
|
+
defaultScope: string;
|
|
158
|
+
}): Promise<RegistryAccessResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.ConsoleOnboardingCancelled=void 0,exports.selectConsoleProject=a,exports.writeProjectLink=s,exports.establishProjectLink=c,exports.projectCreateIdempotencyKey=m,exports.registryAccessTokenName=w,exports.acquireRegistryAccess=g;const e=require("node:crypto"),t=require("node:os"),n=require("node:path"),i=require("./console-api"),o=require("./console-projects"),r=require("./project-link-manifest");class ConsoleOnboardingCancelled extends Error{constructor(e="Vendure Console setup was cancelled."){super(e),this.name="ConsoleOnboardingCancelled"}}async function a(e){const t=await(0,o.listProjectEditions)(e.api),n=t.length>0?await e.interaction.chooseProject(t):void 0,i=null!=n?n:await d(e);return{project:i,...await u(e,i),createdProject:void 0===n}}async function s(e){const t=await(0,o.linkProject)(e.api,e.projectId);return await(0,r.writeProjectLinkManifest)(e.projectRoot,t),await p(e.projectRoot,e.interaction),t}async function c(e){if(e.reuseExistingLink){const t=await l(e);if(t)return t}const t=await a(e);return{manifest:await s({...e,projectId:t.project.id}),edition:t.edition,source:t.source,createdProject:t.createdProject,reusedLink:!1}}async function l(e){const t=await(0,r.readProjectLinkManifest)(e.projectRoot);if("invalid"===t.status)return void e.interaction.warn(`${t.reason} Linking this project again.`);if("missing"===t.status)return;const n=(await(0,o.listProjectEditions)(e.api)).find(e=>e.id===t.manifest.project.id);if(n)return e.interaction.report(`Already linked to ${t.manifest.project.name} in ${t.manifest.account.name}.`),{manifest:t.manifest,edition:n.edition,source:n.source,createdProject:!1,reusedLink:!0};e.interaction.warn(`${t.manifest.project.name} is no longer an active Project on this account. Linking this project again.`)}async function d(e){var t;const n=(await e.interaction.newProjectName()).trim();if(!n)throw new ConsoleOnboardingCancelled("No Project name was given.");let r;try{r=await(0,o.createProject)(e.api,{name:n,idempotencyKey:null!==(t=e.idempotencyKey)&&void 0!==t?t:m(e.projectRoot,n)})}catch(e){if((0,i.isPermissionDenied)(e))throw new Error('This Console session cannot create Projects. Ask an administrator for the "console:project:create" Permission, or create the Project in Vendure Console and run this again.');if((0,i.isConsoleConflict)(e,"project.idempotency_conflict"))throw new Error("A Project was already created for this setup under a different name. Open Vendure Console, then run this again and select it.");throw e}if("active"!==r.state)throw new Error(`The Project "${r.name}" is archived. Restore it in Vendure Console, or run this again with a different name.`);return e.interaction.report(`Created the Project ${r.name}.`),{id:r.id,name:r.name,edition:null,source:"none"}}async function u(e,t){if(t.edition)return e.interaction.report(`${t.name} is on the ${t.edition} edition.`),{edition:t.edition,source:t.source};let n;try{n=await(0,o.getProjectEvaluation)(e.api,t.id)}catch(n){if(!(0,i.isPermissionDenied)(n))throw n;return e.interaction.warn(`${t.name} has no edition yet, and this Console session cannot read its Evaluation eligibility.`),{edition:null,source:t.source}}if("available"!==n.startState||0===n.offerCodes.length)return e.interaction.warn(`${t.name} has no edition yet and cannot start an Evaluation${n.nextEligibleAt?` before ${n.nextEligibleAt}`:""}. Choose a plan in Vendure Console.`),{edition:null,source:t.source};const r=await e.interaction.chooseEvaluation(n.offerCodes);if(!r)return{edition:null,source:t.source};try{return await f(e,t,r)?{edition:r,source:"evaluation"}:{edition:null,source:t.source}}catch(n){if(!(0,i.isPermissionDenied)(n))throw n;return e.interaction.warn('This Console session cannot start an Evaluation. Ask an administrator for the "console:evaluation:manage" Permission.'),{edition:null,source:t.source}}}async function f(e,t,n){try{const i=await(0,o.startProjectEvaluation)(e.api,t.id,n);return e.interaction.report("existing"===i.outcome?`${t.name} already has a ${i.offerCode} Evaluation, ending ${i.expiresAt}.`:`Started a ${i.offerCode} Evaluation on ${t.name}, ending ${i.expiresAt}.`),!0}catch(t){const n=(0,o.evaluationDenial)(t);if(!n)throw t;return e.interaction.warn(`${n.message}${n.nextEligibleAt?` Next eligible ${n.nextEligibleAt}.`:""}`),!1}}async function p(e,t){const n=await(0,r.ensureProjectLinkGitignore)(e);"failed"===n.kind&&t.warn(`Could not update ${n.path}: ${n.reason}. Ignore .vendure/* and commit .vendure/project.json by hand.`)}function m(t,i){return(0,e.createHash)("sha256").update(`${(0,n.resolve)(t)}\0${i}`).digest("hex").slice(0,32)}function w(e=(0,t.hostname)()){return`vendure-cli ${e}`.slice(0,120)}async function g(e){var t;const{api:n,projectId:r,interaction:a}=e,s=null!==(t=e.tokenName)&&void 0!==t?t:w();let c={registryUrl:e.defaultRegistryUrl,scope:e.defaultScope};try{c=await(0,o.getRegistryAccess)(n,r)}catch(e){if(!(0,i.isPermissionDenied)(e))throw e;return{...c,fallback:"npm-login"}}try{const e=await(0,o.createRegistryAccessToken)(n,r,s);return a.report(`Issued the Registry Access Token "${e.name}".`),{...c,token:e.token}}catch(e){if((0,i.isPermissionDenied)(e))return{...c,fallback:"npm-login"};if(!(0,i.isConsoleConflict)(e,"registry_access_token.name_taken"))throw e}const l=(await(0,o.listRegistryAccessTokens)(n,r)).find(e=>"active"===e.state&&e.name.toLowerCase()===s.toLowerCase());if(!l)return a.warn(`A Registry Access Token named "${s}" already exists but is not active. Rotate it in Vendure Console.`),{...c,fallback:"npm-login"};if(a.warn(`A Registry Access Token named "${s}" already exists. Its secret is shown once and cannot be read again, so getting a usable one means rotating it, which stops the existing one working.`),!await a.confirmRotate(l.name))return{...c,fallback:"npm-login"};const d=await(0,o.rotateRegistryAccessToken)(n,r,l.id);return a.report(`Rotated the Registry Access Token "${d.name}".`),{...c,token:d.token}}exports.ConsoleOnboardingCancelled=ConsoleOnboardingCancelled;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Console web app. This is the only origin a login may open in a browser:
|
|
3
|
+
* the user signs in to Console, not to the API and not to an identity provider
|
|
4
|
+
* the CLI picked. Anchoring the visible entry point here means the address bar
|
|
5
|
+
* shows a host the user already trusts.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_CONSOLE_APP_ORIGIN = "https://console.vendure.io";
|
|
8
|
+
/** The Console API. Only the token exchange talks to it, never the browser. */
|
|
9
|
+
export declare const DEFAULT_CONSOLE_API_ORIGIN = "https://api.vendure.io";
|
|
10
|
+
/** Hosts `consoleApiOrigin` accepts, so a caller can name them without restating them. */
|
|
11
|
+
export declare const TRUSTED_API_HOSTS: string[];
|
|
12
|
+
/** The Console web app origin a login may open. Loopback is allowed for dev and tests. */
|
|
13
|
+
export declare function consoleAppOrigin(value?: string): string;
|
|
14
|
+
/** The Console API origin the token exchange may call. */
|
|
15
|
+
export declare function consoleApiOrigin(value?: string): string;
|
|
16
|
+
/** Select the matching remote Console app and API, while allowing explicit local development origins. */
|
|
17
|
+
export declare function consoleOrigins(options: {
|
|
18
|
+
appOrigin?: string;
|
|
19
|
+
apiOrigin?: string;
|
|
20
|
+
}): {
|
|
21
|
+
appOrigin: string;
|
|
22
|
+
apiOrigin: string;
|
|
23
|
+
};
|
package/dist/origins.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.TRUSTED_API_HOSTS=exports.DEFAULT_CONSOLE_API_ORIGIN=exports.DEFAULT_CONSOLE_APP_ORIGIN=void 0,exports.consoleAppOrigin=i,exports.consoleApiOrigin=s,exports.consoleOrigins=p,exports.DEFAULT_CONSOLE_APP_ORIGIN="https://console.vendure.io",exports.DEFAULT_CONSOLE_API_ORIGIN="https://api.vendure.io";const e="https://staging.console.vendure.io",o="https://staging.api.vendure.io",n=["console.vendure.io","staging.console.vendure.io"];function t(e){return["localhost","127.0.0.1","::1"].includes(e)||e.endsWith(".localhost")}function r(e,o,n){let r;try{r=new URL(e)}catch{throw new Error(`The ${n} URL is invalid.`)}const i=o.includes(r.hostname),s=t(r.hostname);if(!i&&!s||i&&"https:"!==r.protocol||s&&!["http:","https:"].includes(r.protocol)||r.username||r.password||"/"!==r.pathname||r.search||r.hash)throw new Error(`The ${n} URL is not trusted.`);return r.origin}function i(e=exports.DEFAULT_CONSOLE_APP_ORIGIN){return r(e,n,"Vendure Console")}function s(e=exports.DEFAULT_CONSOLE_API_ORIGIN){return r(e,exports.TRUSTED_API_HOSTS,"Vendure Console API")}function p(n){var t;const r=void 0===n.appOrigin?void 0:i(n.appOrigin),p=s(null!==(t=n.apiOrigin)&&void 0!==t?t:r===e?o:void 0),O=null!=r?r:p===o?e:exports.DEFAULT_CONSOLE_APP_ORIGIN;if(O===exports.DEFAULT_CONSOLE_APP_ORIGIN&&p===o||O===e&&p===exports.DEFAULT_CONSOLE_API_ORIGIN)throw new Error("The Vendure Console app and API origins must belong to the same environment.");return{appOrigin:O,apiOrigin:p}}exports.TRUSTED_API_HOSTS=["api.vendure.io","staging.api.vendure.io"];
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ConsoleApi } from './console-api';
|
|
2
|
+
import type { ProjectEditionSource } from './console-projects';
|
|
3
|
+
/**
|
|
4
|
+
* `GET /v1/projects/:id/product-access/summary`.
|
|
5
|
+
*
|
|
6
|
+
* The one answer to "what may this Project use, and why", at one instant.
|
|
7
|
+
* Console resolves the precedence between a support override, a subscription
|
|
8
|
+
* and an Evaluation before it answers, so a reader never repeats those rules.
|
|
9
|
+
*
|
|
10
|
+
* Read-only, and the only Console call an edition preflight needs to decide
|
|
11
|
+
* whether a target edition is already paid for. `listProjectEditions` answers a
|
|
12
|
+
* smaller question: it names the edition, not the Capability set behind it, nor
|
|
13
|
+
* the billing phase that is about to take the access away.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here authorizes anything. The signed Entitlement Grant the running
|
|
16
|
+
* server verifies is the authorization source; this is what the operator is
|
|
17
|
+
* entitled to buy into and deploy.
|
|
18
|
+
*/
|
|
19
|
+
export interface ProductAccessSummary {
|
|
20
|
+
/** Where the access comes from. `none` means the Project has no access. */
|
|
21
|
+
source: ProjectEditionSource;
|
|
22
|
+
/** The Product Offer behind the access, or null when a bare override granted Capabilities. */
|
|
23
|
+
offerCode: string | null;
|
|
24
|
+
/** Every Capability key the Project may use. */
|
|
25
|
+
capabilities: readonly string[];
|
|
26
|
+
/** `non_production` access cannot carry a production deployment. */
|
|
27
|
+
environmentScope: 'all' | 'non_production';
|
|
28
|
+
/** Earliest moment this answer can change. Not a display date. */
|
|
29
|
+
validUntil: string | null;
|
|
30
|
+
evaluation: ProductAccessEvaluation | null;
|
|
31
|
+
subscription: ProductAccessSubscription | null;
|
|
32
|
+
/** Registry access ends here while a subscription is in its cure period. */
|
|
33
|
+
cureEndsAt: string | null;
|
|
34
|
+
/** Runtime access ends here while a subscription is in cure or grace. */
|
|
35
|
+
graceEndsAt: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ProductAccessEvaluation {
|
|
38
|
+
offerCode: string;
|
|
39
|
+
status: 'active' | 'expired';
|
|
40
|
+
expiresAt: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ProductAccessSubscription {
|
|
43
|
+
offerCode: string;
|
|
44
|
+
/** `paid` is the healthy phase. The rest are on the way to losing access. */
|
|
45
|
+
accessPhase: 'paid' | 'cure' | 'runtime_grace' | 'none';
|
|
46
|
+
endsAt: string | null;
|
|
47
|
+
}
|
|
48
|
+
export declare function getProductAccessSummary(api: ConsoleApi, projectId: string): Promise<ProductAccessSummary>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";async function e(e,o){const c=r(await e.request(`/v1/projects/${encodeURIComponent(o)}/product-access/summary`),"the Product Access summary");if(!Array.isArray(c.capabilities))throw a("the granted Capability list");return{source:i(c.source,["override","subscription","evaluation","none"],"a Product Access source"),offerCode:s(c.offerCode,"a Product Offer code"),capabilities:c.capabilities.filter(e=>"string"==typeof e),environmentScope:i(c.environmentScope,["all","non_production"],"an environment scope"),validUntil:s(c.validUntil,"a Product Access expiry"),evaluation:null==c.evaluation?null:t(c.evaluation),subscription:null==c.subscription?null:n(c.subscription),cureEndsAt:s(c.cureEndsAt,"a cure deadline"),graceEndsAt:s(c.graceEndsAt,"a grace deadline")}}function t(e){const t=r(e,"the Evaluation state");return{offerCode:o(t.offerCode,"an Evaluation offer"),status:i(t.status,["active","expired"],"an Evaluation status"),expiresAt:o(t.expiresAt,"an Evaluation expiry")}}function n(e){const t=r(e,"the subscription state");return{offerCode:o(t.offerCode,"a subscription offer"),accessPhase:i(t.accessPhase,["paid","cure","runtime_grace","none"],"a subscription access phase"),endsAt:s(t.endsAt,"a subscription end")}}function r(e,t){if(!e||"object"!=typeof e||Array.isArray(e))throw a(t);return e}function o(e,t){if("string"!=typeof e||0===e.length)throw a(t);return e}function s(e,t){return null==e?null:o(e,t)}function i(e,t,n){if("string"!=typeof e||!t.includes(e))throw a(n);return e}function a(e){return new Error(`Vendure Console returned ${e} in an unexpected shape.`)}Object.defineProperty(exports,"__esModule",{value:!0}),exports.getProductAccessSummary=e;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Project Link Manifest, `<projectRoot>/.vendure/project.json`.
|
|
3
|
+
*
|
|
4
|
+
* Identity metadata and nothing else. It names the Project and the Customer
|
|
5
|
+
* Account a checkout belongs to, so it is meant to be committed, and it holds
|
|
6
|
+
* no secret of either kind: the account-level Console session lives under
|
|
7
|
+
* `~/.vendure` and the project-scoped Environment Credential lives beside this
|
|
8
|
+
* file in `.vendure/credentials.json`.
|
|
9
|
+
*
|
|
10
|
+
* The shape is Console's `ProjectLinkManifestDto` and is also what
|
|
11
|
+
* `vendure console link` writes in `@vendure/cli`, so a manifest written by
|
|
12
|
+
* either tool reads in both.
|
|
13
|
+
*/
|
|
14
|
+
export interface ProjectLinkManifest {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
project: {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
account: {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
link: {
|
|
25
|
+
id: string;
|
|
26
|
+
protocolVersion: 1;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export type ProjectLinkManifestRead = {
|
|
30
|
+
status: 'missing';
|
|
31
|
+
path: string;
|
|
32
|
+
} | {
|
|
33
|
+
status: 'invalid';
|
|
34
|
+
path: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
} | {
|
|
37
|
+
status: 'valid';
|
|
38
|
+
path: string;
|
|
39
|
+
manifest: ProjectLinkManifest;
|
|
40
|
+
};
|
|
41
|
+
export declare const PROJECT_LINK_MANIFEST_RELATIVE_PATH: string;
|
|
42
|
+
/**
|
|
43
|
+
* `.vendure/*` is ignored and the manifest is un-ignored, so a checkout commits
|
|
44
|
+
* the identity metadata and never the two credential files that sit next to it.
|
|
45
|
+
* Both lines match `@vendure/cli`'s own rules exactly, so the two tools do not
|
|
46
|
+
* fight over the same `.gitignore`.
|
|
47
|
+
*/
|
|
48
|
+
export declare const PROJECT_LINK_IGNORE_RULE: string;
|
|
49
|
+
export declare const PROJECT_LINK_KEEP_RULE: string;
|
|
50
|
+
export declare function projectLinkManifestPath(projectRoot: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Read the manifest without throwing.
|
|
53
|
+
*
|
|
54
|
+
* The three outcomes are acted on differently, so they are reported
|
|
55
|
+
* separately: a missing manifest is an unlinked project, an invalid one is a
|
|
56
|
+
* file a person may have edited, and only the third can be trusted.
|
|
57
|
+
*/
|
|
58
|
+
export declare function readProjectLinkManifest(projectRoot: string): Promise<ProjectLinkManifestRead>;
|
|
59
|
+
/** Validate a manifest Console returned, or throw. */
|
|
60
|
+
export declare function parseProjectLinkManifest(value: unknown): ProjectLinkManifest;
|
|
61
|
+
/**
|
|
62
|
+
* Write the manifest through a temporary file and a rename, so a reader never
|
|
63
|
+
* observes a half-written one. The temporary file is opened `wx` at 0600, which
|
|
64
|
+
* matches what `vendure console link` in `@vendure/cli` writes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function writeProjectLinkManifest(projectRoot: string, manifest: ProjectLinkManifest): Promise<string>;
|
|
67
|
+
export type ProjectLinkGitignoreResult = {
|
|
68
|
+
kind: 'unchanged';
|
|
69
|
+
path: string;
|
|
70
|
+
} | {
|
|
71
|
+
kind: 'created';
|
|
72
|
+
path: string;
|
|
73
|
+
} | {
|
|
74
|
+
kind: 'updated';
|
|
75
|
+
path: string;
|
|
76
|
+
} | {
|
|
77
|
+
kind: 'failed';
|
|
78
|
+
path: string;
|
|
79
|
+
reason: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Make sure `.gitignore` ignores everything under `.vendure` except the
|
|
83
|
+
* manifest. Never throws: a checkout that is not a git repository, or one whose
|
|
84
|
+
* `.gitignore` cannot be written, is not a reason to fail a link that already
|
|
85
|
+
* succeeded.
|
|
86
|
+
*/
|
|
87
|
+
export declare function ensureProjectLinkGitignore(projectRoot: string): Promise<ProjectLinkGitignoreResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Add the two rules to `content`, replacing a blanket `.vendure` rule if one is
|
|
90
|
+
* already there. A scaffold that ignores the whole directory would otherwise
|
|
91
|
+
* keep the manifest out of the repository, which is the opposite of the intent.
|
|
92
|
+
*/
|
|
93
|
+
export declare function applyProjectLinkGitignoreRules(content: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.PROJECT_LINK_KEEP_RULE=exports.PROJECT_LINK_IGNORE_RULE=exports.PROJECT_LINK_MANIFEST_RELATIVE_PATH=void 0,exports.projectLinkManifestPath=s,exports.readProjectLinkManifest=a,exports.parseProjectLinkManifest=c,exports.writeProjectLinkManifest=E,exports.ensureProjectLinkGitignore=u,exports.applyProjectLinkGitignoreRules=p;const t=require("node:crypto"),e=require("node:fs/promises"),n=require("node:path");exports.PROJECT_LINK_MANIFEST_RELATIVE_PATH=(0,n.join)(".vendure","project.json");const r=exports.PROJECT_LINK_MANIFEST_RELATIVE_PATH.split(n.sep).join("/"),i=n.posix.dirname(r);exports.PROJECT_LINK_IGNORE_RULE=`${i}/*`,exports.PROJECT_LINK_KEEP_RULE=`!${r}`;const o=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function s(t){return(0,n.join)(t,exports.PROJECT_LINK_MANIFEST_RELATIVE_PATH)}async function a(t){const n=s(t);let r,i;try{r=await(0,e.readFile)(n,"utf8")}catch(t){return t instanceof Error&&"code"in t&&"ENOENT"===t.code?{status:"missing",path:n}:{status:"invalid",path:n,reason:"The Project Link Manifest could not be read."}}try{i=JSON.parse(r)}catch{return{status:"invalid",path:n,reason:"The Project Link Manifest is not valid JSON."}}return d(i)?{status:"valid",path:n,manifest:i}:{status:"invalid",path:n,reason:"The Project Link Manifest has an unsupported or malformed structure."}}function c(t){if(!d(t))throw new Error("Vendure Console returned a malformed Project Link Manifest.");return t}async function E(r,i){c(i);const o=s(r),a=(0,n.join)(r,".vendure"),E=(0,n.join)(a,`.project-${(0,t.randomUUID)()}.tmp`);let u;try{await(0,e.mkdir)(a,{recursive:!0}),u=await(0,e.open)(E,"wx",384),await u.writeFile(`${JSON.stringify(i,null,2)}\n`,"utf8"),await u.sync(),await u.close(),u=void 0,await(0,e.rename)(E,o)}catch{throw await(null==u?void 0:u.close().catch(()=>{})),await(0,e.unlink)(E).catch(()=>{}),new Error("Could not write the Project Link Manifest.")}return o}async function u(t){const r=(0,n.join)(t,".gitignore");try{let t;try{t=await(0,e.readFile)(r,"utf8")}catch(t){if(!(t instanceof Error&&"code"in t&&"ENOENT"===t.code))throw t;return await(0,e.writeFile)(r,`${exports.PROJECT_LINK_IGNORE_RULE}\n${exports.PROJECT_LINK_KEEP_RULE}\n`,"utf8"),{kind:"created",path:r}}const n=p(t);return n===t?{kind:"unchanged",path:r}:(await(0,e.writeFile)(r,n,"utf8"),{kind:"updated",path:r})}catch(t){return{kind:"failed",path:r,reason:t instanceof Error?t.message:String(t)}}}function p(t){const e=t.includes("\r\n")?"\r\n":"\n",n=t.split(/\r?\n/);if(n.includes(exports.PROJECT_LINK_IGNORE_RULE)&&n.includes(exports.PROJECT_LINK_KEEP_RULE))return t;const r=n.findIndex(t=>t===i||t===`${i}/`);if(-1!==r)return n.splice(r,1,exports.PROJECT_LINK_IGNORE_RULE,exports.PROJECT_LINK_KEEP_RULE),n.join(e);let o=t;return o.length>0&&!o.endsWith("\n")&&(o+=e),o.length>0&&!/(?:\r?\n){2}$/.test(o)&&(o+=e),`${o}${exports.PROJECT_LINK_IGNORE_RULE}${e}${exports.PROJECT_LINK_KEEP_RULE}${e}`}function d(t){return!!l(t,["schemaVersion","project","account","link"])&&1===t.schemaVersion&&_(t.project)&&_(t.account)&&l(t.link,["id","protocolVersion"])&&"string"==typeof t.link.id&&o.test(t.link.id)&&1===t.link.protocolVersion}function _(t){return l(t,["id","name"])&&"string"==typeof t.id&&o.test(t.id)&&"string"==typeof t.name&&t.name.trim().length>0}function l(t,e){if(!t||"object"!=typeof t||Array.isArray(t))return!1;const n=Object.keys(t);return n.length===e.length&&n.every(t=>e.includes(t))}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Vendure Platform registry, in one place.
|
|
3
|
+
*
|
|
4
|
+
* Both `@vendure-platform/create` and `vendure console link` write a project
|
|
5
|
+
* `.npmrc`, so both need this. It lives here rather than in either of them
|
|
6
|
+
* because a second copy is a second thing to change at the next rename.
|
|
7
|
+
*
|
|
8
|
+
* This is the fallback. A caller that reaches Console asks
|
|
9
|
+
* `GET /v1/projects/:id/registry-access` and uses the origin it answers with,
|
|
10
|
+
* because that is the origin the Access Token it issues is good for.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_REGISTRY_URL = "https://registry.vendure.io/";
|
|
13
|
+
/** The npm scope the registry distributes. */
|
|
14
|
+
export declare const REGISTRY_SCOPE = "@vendure-platform";
|
|
15
|
+
/**
|
|
16
|
+
* The registry to write, honouring `VENDURE_REGISTRY_URL`. That override points
|
|
17
|
+
* a scaffold or a link at a local Verdaccio without patching the result
|
|
18
|
+
* afterwards, which is how the create-flow harness runs.
|
|
19
|
+
*/
|
|
20
|
+
export declare function registryUrl(env?: NodeJS.ProcessEnv): string;
|
|
21
|
+
/** `url` without its trailing slash, which is how npm commands read best. */
|
|
22
|
+
export declare function registryLoginUrl(url?: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* The line prefix npm looks a registry credential up by.
|
|
25
|
+
*
|
|
26
|
+
* The port is part of the key, so a registry on one derives from `host` rather
|
|
27
|
+
* than `hostname`. Dropping it writes a line npm never reads, which is how a
|
|
28
|
+
* scaffold against a local registry installs unauthenticated.
|
|
29
|
+
*/
|
|
30
|
+
export declare function registryAuthTokenKey(url?: string): string;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";function e(e=process.env){var r;return(null===(r=e.VENDURE_REGISTRY_URL)||void 0===r?void 0:r.trim())||exports.DEFAULT_REGISTRY_URL}function r(r=e()){return r.replace(/\/+$/,"")}function t(r=e()){return`//${new URL(r).host}/:_authToken=`}Object.defineProperty(exports,"__esModule",{value:!0}),exports.REGISTRY_SCOPE=exports.DEFAULT_REGISTRY_URL=void 0,exports.registryUrl=e,exports.registryLoginUrl=r,exports.registryAuthTokenKey=t,exports.DEFAULT_REGISTRY_URL="https://registry.vendure.io/",exports.REGISTRY_SCOPE="@vendure-platform";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a lock file may sit untouched before another participant may take it
|
|
3
|
+
* over.
|
|
4
|
+
*
|
|
5
|
+
* A holder touches its own lock while it works, so the budget measures a holder
|
|
6
|
+
* that has stopped rather than one that is slow. The README section "Sharing
|
|
7
|
+
* the credential across processes" carries the protocol these budgets belong
|
|
8
|
+
* to.
|
|
9
|
+
*/
|
|
10
|
+
export declare const CONSOLE_SESSION_LOCK_STALE_MS = 30000;
|
|
11
|
+
/**
|
|
12
|
+
* How long a participant waits for a lock somebody else holds.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately not shorter than the stale budget. If it were, a waiter could
|
|
15
|
+
* give up while the holder is dead but not yet stale, and "I gave up waiting"
|
|
16
|
+
* would stop meaning "the holder is gone".
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONSOLE_SESSION_LOCK_WAIT_MS = 30000;
|
|
19
|
+
/** The lock guarding `~/.vendure/console-credentials.json`, beside the file it guards. */
|
|
20
|
+
export declare function consoleSessionLockPath(home?: string): string;
|
|
21
|
+
export interface ConsoleSessionLockOptions {
|
|
22
|
+
/** Overrides `os.homedir()`. Tests point this at a temporary directory. */
|
|
23
|
+
home?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The budgets are part of the protocol rather than a user setting. They are
|
|
26
|
+
* open here so tests can drive the stale and give-up paths without waiting
|
|
27
|
+
* half a minute for each one. `waitMs` is raised to `staleMs` when it is
|
|
28
|
+
* smaller, because the protocol does not allow the shorter of the two.
|
|
29
|
+
*/
|
|
30
|
+
staleMs?: number;
|
|
31
|
+
waitMs?: number;
|
|
32
|
+
pollMs?: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run `work` while holding the Console session lock.
|
|
36
|
+
*
|
|
37
|
+
* Every step that spends a refresh token or rewrites the credential file has to
|
|
38
|
+
* go through here, including the ones in other tools. Console rotates the
|
|
39
|
+
* refresh token on use, so a second process presenting the same token is
|
|
40
|
+
* refused and left with nothing, and guarding only some of the call sites
|
|
41
|
+
* leaves the double spend reachable through the rest. The protocol is written
|
|
42
|
+
* down in the README so a participant in another language can join in.
|
|
43
|
+
*
|
|
44
|
+
* The lock is not reentrant. The exported session functions take it themselves,
|
|
45
|
+
* so taking it around one of them waits out the whole wait budget and then
|
|
46
|
+
* fails: the outer lock is held by a process that is alive and beating, which
|
|
47
|
+
* is the one thing a waiter never breaks.
|
|
48
|
+
*/
|
|
49
|
+
export declare function withConsoleSessionLock<T>(options: ConsoleSessionLockOptions, work: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.CONSOLE_SESSION_LOCK_WAIT_MS=exports.CONSOLE_SESSION_LOCK_STALE_MS=void 0,exports.consoleSessionLockPath=i,exports.withConsoleSessionLock=s;const t=require("node:crypto"),n=require("node:fs/promises"),e=require("node:path"),o=require("node:timers/promises"),r=require("./credential-store");exports.CONSOLE_SESSION_LOCK_STALE_MS=3e4,exports.CONSOLE_SESSION_LOCK_WAIT_MS=3e4;const a=50;function i(t){return(0,e.join)((0,r.consoleConfigDir)(t),"console-credentials.lock")}async function s(t,n){var e;const o=await u(t),r=c(o,null!==(e=t.staleMs)&&void 0!==e?e:exports.CONSOLE_SESSION_LOCK_STALE_MS);try{return await n()}finally{clearInterval(r),await w(o)}}function c(t,e){const o=setInterval(()=>{(async()=>{const e=await d(t.path);if((null==e?void 0:e.token)!==t.token)return;const o=new Date;await(0,n.utimes)(t.path,o,o)})().catch(()=>{})},Math.max(1,Math.round(e/3)));return o.unref(),o}async function u(e){var s,c,u;const w=i(e.home),d=null!==(s=e.staleMs)&&void 0!==s?s:exports.CONSOLE_SESSION_LOCK_STALE_MS,h=Math.max(null!==(c=e.waitMs)&&void 0!==c?c:exports.CONSOLE_SESSION_LOCK_WAIT_MS,d),O=null!==(u=e.pollMs)&&void 0!==u?u:a,f=(0,t.randomUUID)(),_=Date.now()+h;await(0,n.mkdir)((0,r.consoleConfigDir)(e.home),{recursive:!0,mode:448});const p=async()=>{if(await S(w,d))return await l(w,f)?{path:w,token:f}:void 0};for(;;){if(await l(w,f))return{path:w,token:f};const t=await p();if(t)return t;if(Date.now()>=_){const t=await p();if(t)return t;throw new Error("Another Vendure process is using the Console session. Try again.")}await(0,o.setTimeout)(O)}}async function l(t,e){let o;try{o=await(0,n.open)(t,"wx",384)}catch(t){const{code:n}=t;if("EEXIST"===n)return!1;throw new Error(`Could not lock the Vendure Console credential (${null!=n?n:"unknown error"}).`)}try{const t={token:e,pid:process.pid,acquiredAt:(new Date).toISOString()};return await o.writeFile(`${JSON.stringify(t)}\n`,"utf8"),!0}catch{throw await(0,n.unlink)(t).catch(()=>{}),new Error("Could not lock the Vendure Console credential.")}finally{await o.close().catch(()=>{})}}async function S(e,o){const r=await d(e);if(!r)return!0;if(Date.now()-Math.floor(r.mtimeMs)<o)return!1;const a=`${e}.${(0,t.randomUUID)()}`;try{await(0,n.rename)(e,a)}catch{return!0}const i=await d(a);return i&&i.token!==r.token&&await(0,n.link)(a,e).catch(()=>{}),await(0,n.unlink)(a).catch(()=>{}),!0}async function w(t){const e=await d(t.path);(null==e?void 0:e.token)===t.token&&await(0,n.unlink)(t.path).catch(()=>{})}async function d(t){try{const e=await(0,n.stat)(t);let o;try{const e=JSON.parse(await(0,n.readFile)(t,"utf8"));"string"==typeof(null==e?void 0:e.token)&&(o=e.token)}catch{}return{token:o,mtimeMs:e.mtimeMs}}catch{return}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type BrowserLoginOptions } from './browser-auth';
|
|
2
|
+
export interface ConsoleSessionOptions {
|
|
3
|
+
/** Overrides `os.homedir()`. Tests point this at a temporary directory. */
|
|
4
|
+
home?: string;
|
|
5
|
+
appOrigin?: string;
|
|
6
|
+
apiOrigin?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
now?: () => Date;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A usable access token from the stored credential, or `undefined` when there is
|
|
12
|
+
* none and the caller has to log in.
|
|
13
|
+
*
|
|
14
|
+
* A credential that is still valid is returned as-is, without taking the lock:
|
|
15
|
+
* that read spends nothing and writes nothing, and every command would
|
|
16
|
+
* otherwise queue behind every other one. One that has expired but carries a
|
|
17
|
+
* refresh token is refreshed and written back under the lock, so a returning
|
|
18
|
+
* user is not sent to the browser every time their access token rolls over. A
|
|
19
|
+
* refresh that fails is treated as no credential rather than an error: the
|
|
20
|
+
* recovery is the same login the caller would offer anyway.
|
|
21
|
+
*
|
|
22
|
+
* Failing to take the lock is not that, and it throws. It says nothing about
|
|
23
|
+
* the session, and answering "no credential" would send a person to the browser
|
|
24
|
+
* to log in over a session that is fine and is being refreshed as they are
|
|
25
|
+
* asked.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveStoredConsoleToken(options?: ConsoleSessionOptions): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Refresh the stored credential and write the new one back, returning the fresh
|
|
30
|
+
* access token.
|
|
31
|
+
*
|
|
32
|
+
* Split out from `resolveStoredConsoleToken` because a token can also expire
|
|
33
|
+
* mid-flow, between two calls of a command that is already running. There the
|
|
34
|
+
* caller wants the failure rather than a silent `undefined`, so this throws.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refreshStoredConsoleToken(options?: ConsoleSessionOptions): Promise<string>;
|
|
37
|
+
/** Run the browser login, persist the result, and return the access token. */
|
|
38
|
+
export declare function loginAndStoreConsoleToken(options: ConsoleSessionOptions & Omit<BrowserLoginOptions, 'appOrigin' | 'apiOrigin' | 'fetch' | 'now'>): Promise<string>;
|